JWT學習


參考轉載:https://www.jianshu.com/p/1ebfc1d78928

什么是JWT

JSON Web Token (JWT)是一種基於 token 的認證方案。
簡單的說,JWT就是一種Token的編碼算法,服務器端負責根據一個密碼和算法生成Token,然后發給客戶端,客戶端只負責后面每次請求都在HTTP header里面帶上這個Token,服務器負責驗證這個Token是不是合法的,有沒有過期等,並可以解析出subject和claim里面的數據。
注意:JWT里面的數據是BASE64編碼的,沒有加密,因此不要放如敏感數據

一個JWT由三部分組成:

header(頭部)-----base64編碼的Json字符串

Payload(載荷)---base64編碼的Json字符串

Signature(簽名)---使用指定算法,通過Header和Playload加鹽計算的字符串

各部分以“.”分割,如

eyJhbGciOiJIUzUxMiJ9.eyJjcnQiOjE1MjgzNDM4OTgyNjgsImV4cCI6MTUyODM0MzkxOCwidXNlcm5hbWUiOiJ0b20ifQ.E-0jxKxLICWgcFEwNwQ4pfhdMzchcHmsd8G_BTsWgkUmVwPzDd7jJlf94cAdtbwTLMm27ouYYzTTxMXq7W1jvQ
(也可以簡化為下面這樣的結構:
base64url_encode(Header) + '.' + base64url_encode(Claims) + '.' + base64url_encode(Signature))
通過base64解碼就可以獲得
Header:{"alg":"HSS12"}
Payload:
{"crt":1528343898268,"exp":1528343918,"username":"tom"}

說明:

1、JWT只通過算法實現對Token合法性的驗證,不依賴數據庫,Memcached的等存儲系統,因此可以做到跨服務器驗證,只要密鑰和算法相同,不同服務器程序生成的Token可以互相驗證。

2、退出登錄, 只要客戶端端把Token丟棄就可以了,服務器端不需要廢棄Token

3、服務器端提供刷新Token的接口, 客戶端負責按一定的邏輯刷新服務器Token

4、REST API是無狀態的,意味着服務器端每次請求都是獨立的,即不依賴以前請求的結果,因此也不應該依賴JWT token做業務查詢, 應該在請求報文中單獨加個userid 字段。

5、為了做用戶水平越權的檢查,可以在業務層判斷傳入的userid和從JWT token中解析出的userid是否一致, 有些業務可能會允許查不同用戶的數據

生成Token:

public static String genarateToken(String username){  

 Map claims= new HashMap<>(); claims.put(CLAIM_KEY_USERNAME,username); claims.put(CLAIM_KEY_CREATE_TIME,new Date(System.currentTimeMillis())); return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, SIGNING_KEY) //.compressWith(CompressionCodecs.DEFLATE) .compact();
}
源碼部分
Jwts.builder() 返回了一個 DefaultJwtBuilder()
DefaultJwtBuilder屬性:
 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private Header header; //頭部 private Claims claims; //聲明 private String payload; //載荷 private SignatureAlgorithm algorithm; //簽名算法 private Key key; //簽名key private byte[] keyBytes; //簽名key的字節數組 private CompressionCodec compressionCodec; //壓縮算法
DefaultJwtBuilder包含了一些Header和Payload的一些常用設置方法

從頭說起

  • setHeader() 有兩種參數形式,一種是Header接口的實現,一種是Map。其中Header接口也繼承自Map。如果以第二種形式(即Map)作為參數,在setHeader的時候會生成默認的Header接口實現DefaultHeader對象。兩種參數形式調用setHeader(),都會令Header重新賦值。即:
        this.header = header;
        或者
        this.header = new DefaultHeader(header);
  • setHeaderParam() 和 setHeaderParams() 向Header追加參數
    兩個方法都使用ensureHeader() 方法(返回當前header 如果不存在則創建DefaultHeader)

如果即不設置簽名,也不進行壓縮,header是不是就應該沒有了呢?
不是。即時不進行簽名,alg也應該存在,不然對其進行解析會出錯。
在生成jwt的時候,如果不設置簽名,那么header中的alg應該為none。jjwt中compact()方法實現如下:

        if (key != null) { jwsHeader.setAlgorithm(algorithm.getValue()); } else { //no signature - plaintext JWT: jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue()); } 

{"alg":"none"} 

再看載荷

  • 值得注意的是,載荷部分存在兩個屬性:payload和claims。兩個屬性均可作為載荷,jjwt中二者只能設置其一,如果同時設置,在終端方法compact() 中將拋出異常
        //payload和claims均不設置 if (payload == null && Collections.isEmpty(claims)) { throw new IllegalStateException("Either 'payload' or 'claims' must be specified."); } //payload和claims同時設置 if (payload != null && !Collections.isEmpty(claims)) { throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one."); } //簽名key(鹽)不設置 if (key != null && keyBytes != null) { throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one."); } 
  • setPayload() 設置payload,直接賦值
  • setClaims() 設置claims,以參數創建一個新Claims對象,直接賦值
  • claim() 如果builder中Claims屬性為空,則創建DefaultClaims對象,並把鍵值放入;如果Claims屬性不為空,獲取之后判斷鍵值,存在則更新,不存在則直接放入。

還提供了 JWT標准 7個保留聲明(Reserved claims)的設置方法,7個聲明都是可選的,也就是說可以不用設置。

  • setIssuer()
  • setSubject()
  • setAudience()
  • setExpiration()
  • setNotBefore()
  • setIssuedAt()
  • setId()
iss: 簽發者
sub: 面向用戶
aud: 接收者
iat(issued at): 簽發時間
exp(expires): 過期時間
nbf(not before):不能被接收處理時間,在此之前不能被接收處理
jti:JWT ID為web token提供唯一標識

Payload 舉例:

{"sub":"subject","aud":"sina.com","iss":"baidu.com","iat":1528360628,"nbf":1528360631,"jti":"253e6s5e","exp":1528360637} 

當然也可以在Payload中添加一些自定義的屬性claims鍵值對

  • compressWith() 壓縮方法。當載荷過長時可對其進行壓縮。可采用jjwt實現的兩種壓縮方法CompressionCodecs.GZIP和CompressionCodecs.DEFLATE

  • signWith() 簽名方法。兩個參數分別是簽名算法和自定義的簽名Key(鹽)。簽名key可以byte[] 、String及Key的形式傳入。前兩種形式均存入builder的keyBytes屬性,后一種形式存入builder的key屬性。如果是第二種(及String類型)的key,則將其進行base64解碼獲得byte[] 。

builder所需的東西都俱備了,那就可以build了!

  • compact() 生成JWT。過程如下:
  1. 載荷校驗,前文已經提及。
  2. 獲取key。如果是keyBytes則通過keyBytes及算法名生成key對象。
  3. 將所使用簽名算法寫入header。如果使用壓縮,將壓縮算法寫入header。
  4. 將Json形式的header轉為bytes,再Base64編碼
  5. 將Json形式的claims轉為bytes,如果需要壓縮則壓縮,再進行Base64編碼
  6. 拼接header和claims。如果簽名key為空,則不進行簽名(末尾補分隔符" . ");如果簽名key不為空,以拼接的字符串作為參數,按照指定簽名算法進行簽名計算簽名部分 sign(String jwtWithoutSignature),簽名部分同樣也會進行Base64編碼。
  7. 返回完整JWT

jjwt實現的 DefaultJwtSigner 提供了一個帶工廠參數的構造方法。並將jjwt實現的 DefaultSignerFactory
靜態實例傳入,根據不同的簽名算法創建對應的簽名器進行簽名。








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM