JWT單點登錄(源碼學習)

你好我是辰兮,很高興你能來閱讀,本篇給是初學JWT單點登錄後的困惑,現在源碼學習後更加了解JWT的結構,小結下來,獻給初學者,共同成長,一起進步。



一、JWT的結構

JWT的結構是什麼樣的?
在這裏插入圖片描述
token構成包含三個部分:

  • Header 頭部
  • Payload 負載
  • Signature 簽名

在這裏插入圖片描述

//格式如下
xxx.yyy.zzz

在這裏插入圖片描述
①JWT-header(頭信息)

由兩部分組成,令牌類型(即:JWT)散列算法(HMAC、RSASSA、RSASSA-PSS等)

JWT頭部分是一個描述JWT元數據的JSON對象,通常如下所示。

{
"alg": "HS256",
"typ": "JWT"
}

然後,這個JSON被編碼爲Base64Url,形成JWT的第一部分。


②有效載荷payload

有效載荷部分,是JWT的主體內容部分,也是一個JSON對象,包含需要傳遞的數據。

JWT的第二部分是payload,其中包含claims。claims是關於實體(常用的是用戶信息)和其他數據的聲明

claims有三種類型: registered, public, and private claims。

  • Registered claims: 這些是一組預定義的claims,非強制性的,但是推薦使用, iss(發行人), exp(到期時間),sub(主題), aud(觀衆)等;
  • Public claims: 自定義claims,注意不要和JWT註冊表中屬性衝突,這裏可以查看JWT註冊表;
  • Private claims:這些是自定義的claims,用於在同意使用這些claims的各方之間共享信息,它們既不是Registered claims,也不是Public claims;
{
  "sub": "1234567890",
  "name": "Tom",
  "admin": true
}

請注意,默認情況下JWT是未加密的,任何人都可以解讀其內容,因此不要構建隱私信息字段,存放保密信息,以防止信息泄露。

JSON對象也使用Base64 URL算法轉換爲字符串保存。


③簽名哈希

簽名哈希部分是對上面兩部分數據簽名,通過指定的算法生成哈希,以確保數據不會被篡改。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SfcKxwRJSMeKF2QT4fwpMeJf36POkayJV_adQssw6f

在計算出簽名哈希後,JWT頭,有效載荷和簽名哈希的三個部分組合成一個字符串,每個部分用"."分隔,就構成整個JWT對象。


④Base64URL算法

如前所述,JWT頭和有效載荷序列化的算法都用到了Base64URL。該算法和常見Base64算法類似,稍有差別。

作爲令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三個字符是"+","/“和”=",由於在URL中有特殊含義,因此Base64URL中對他們做了替換:"=“去掉,”+“用”-“替換,”/“用”_"替換,這就是Base64URL算法。


二、JWT源碼學習

  • 參考一下常見的代碼初學者可能看上去很複雜,接下來分析一波
//登錄成功之後,需要生成token
String token = Jwts.builder().setSubject("用戶名/用戶信息") //主題,可以放用戶的詳細信息
        .setIssuedAt(new Date()) //token創建時間
        .setExpiration(new Date(System.currentTimeMillis() + 60000)) //token過期時間
        .setId("用戶ID") //用戶ID
        .setClaims(hashMap) //配置角色信息
        .signWith(SignatureAlgorithm.HS256, "WuHan") //加密方式和加密密碼
        .compact();
  • 首先Jwts是Java已經被人寫好的一個被final修飾的類,然後裏面自帶很多方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.jsonwebtoken;

import io.jsonwebtoken.impl.DefaultClaims;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.DefaultJwtBuilder;
import io.jsonwebtoken.impl.DefaultJwtParser;
import java.util.Map;

public final class Jwts {
    private Jwts() {
    }

    public static Header header() {
        return new DefaultHeader();
    }

    public static Header header(Map<String, Object> header) {
        return new DefaultHeader(header);
    }

    public static JwsHeader jwsHeader() {
        return new DefaultJwsHeader();
    }

    public static JwsHeader jwsHeader(Map<String, Object> header) {
        return new DefaultJwsHeader(header);
    }

    public static Claims claims() {
        return new DefaultClaims();
    }

    public static Claims claims(Map<String, Object> claims) {
        return new DefaultClaims(claims);
    }

    public static JwtParser parser() {
        return new DefaultJwtParser();
    }

    public static JwtBuilder builder() {
        return new DefaultJwtBuilder();
    }
}

  • JwtBuilder是一個接口,裏面也自帶很多方法,每一個方法的返回值類型是自己本身,這裏就是給用戶設置相關信息的,所以代碼你看上去是連着set的。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.jsonwebtoken;

import java.security.Key;
import java.util.Date;
import java.util.Map;

public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
    JwtBuilder setHeader(Header var1);

    JwtBuilder setHeader(Map<String, Object> var1);

    JwtBuilder setHeaderParams(Map<String, Object> var1);

    JwtBuilder setHeaderParam(String var1, Object var2);

    JwtBuilder setPayload(String var1);

    JwtBuilder setClaims(Claims var1);

    JwtBuilder setClaims(Map<String, Object> var1);

    JwtBuilder addClaims(Map<String, Object> var1);

    JwtBuilder setIssuer(String var1);

    JwtBuilder setSubject(String var1);

    JwtBuilder setAudience(String var1);

    JwtBuilder setExpiration(Date var1);

    JwtBuilder setNotBefore(Date var1);

    JwtBuilder setIssuedAt(Date var1);

    JwtBuilder setId(String var1);

    JwtBuilder claim(String var1, Object var2);

    JwtBuilder signWith(SignatureAlgorithm var1, byte[] var2);

    JwtBuilder signWith(SignatureAlgorithm var1, String var2);

    JwtBuilder signWith(SignatureAlgorithm var1, Key var2);

    JwtBuilder compressWith(CompressionCodec var1);

    String compact();
}

JWT的解析

try {
    JwtParser parser = Jwts.parser();
    parser.setSigningKey("WuHan");//解析 要和上面“暗號”一樣
    Jws<Claims> claimsJws = parser.parseClaimsJws(token);
    Claims body = claimsJws.getBody();
    String username = body.getSubject();
  //  Object role = body.get("role");

    return true;
} catch (ExpiredJwtException e) {
    e.printStackTrace();
} catch (UnsupportedJwtException e) {
    e.printStackTrace();
} catch (MalformedJwtException e) {
    e.printStackTrace();
} catch (SignatureException e) {
    e.printStackTrace();
} catch (IllegalArgumentException e) {
    e.printStackTrace();
}

setSigningKey() 與builder中籤名方法signWith()對應,parser中的此方法擁有與signWith()方法相同的三種參數形式,用於設置JWT的簽名key,用戶後面對JWT進行解析。

package io.jsonwebtoken;

import java.security.Key;
import java.util.Date;

public interface JwtParser {
    char SEPARATOR_CHAR = '.';

    JwtParser requireId(String var1);

    JwtParser requireSubject(String var1);

    JwtParser requireAudience(String var1);

    JwtParser requireIssuer(String var1);

    JwtParser requireIssuedAt(Date var1);

    JwtParser requireExpiration(Date var1);

    JwtParser requireNotBefore(Date var1);

    JwtParser require(String var1, Object var2);

    JwtParser setClock(Clock var1);

    JwtParser setAllowedClockSkewSeconds(long var1);

    JwtParser setSigningKey(byte[] var1);

    JwtParser setSigningKey(String var1);

    JwtParser setSigningKey(Key var1);

    JwtParser setSigningKeyResolver(SigningKeyResolver var1);

    JwtParser setCompressionCodecResolver(CompressionCodecResolver var1);

    boolean isSigned(String var1);

    Jwt parse(String var1) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    <T> T parse(String var1, JwtHandler<T> var2) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jwt<Header, String> parsePlaintextJwt(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jwt<Header, Claims> parseClaimsJwt(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jws<String> parsePlaintextJws(String var1) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

    Jws<Claims> parseClaimsJws(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}

Claims認證很難解釋清楚,甚至我都找不到一個合適的中文詞語來翻譯它。只好用一個比喻來說明。

  • 在實行社保卡之前,我們去醫院看病的時候,需要拿着身份證去辦理一張就診卡,辦卡的工作人員校驗完你的身份證以後,會將你的個人信息錄入到卡里面。當你去找醫生就診的時候,醫生掃描一下你的就診卡,就知道了你的所有信息。這個就診卡就相當於Claims認證中的token,裏面的每條信息就是一個Claim.
package io.jsonwebtoken;

import java.util.Date;
import java.util.Map;

public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
    String ISSUER = "iss";
    String SUBJECT = "sub";
    String AUDIENCE = "aud";
    String EXPIRATION = "exp";
    String NOT_BEFORE = "nbf";
    String ISSUED_AT = "iat";
    String ID = "jti";

    String getIssuer();

    Claims setIssuer(String var1);

    String getSubject();

    Claims setSubject(String var1);

    String getAudience();

    Claims setAudience(String var1);

    Date getExpiration();

    Claims setExpiration(Date var1);

    Date getNotBefore();

    Claims setNotBefore(Date var1);

    Date getIssuedAt();

    Claims setIssuedAt(Date var1);

    String getId();

    Claims setId(String var1);

    <T> T get(String var1, Class<T> var2);
}

上述Claims接口中定義了一些變量,我找到了相關圖片給你們參考

iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號

在這裏插入圖片描述


三、JWT 的特點小結

在這裏插入圖片描述

(1)JWT 默認是不加密,但也是可以加密的。生成原始 Token 以後,可以用密鑰再加密一次。

(2)JWT 不加密的情況下,不能將祕密數據寫入 JWT。

(3)JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯

(5)JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。爲了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

(6)爲了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。


The best investment is to invest in yourself

在這裏插入圖片描述

2020.06.04 記錄辰兮的第76篇博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章