Spring Boot中使用token:jwt

超頻化石魚發表於2018-11-22

token由3部分組成:Header,Payload,Signature。

其中Header記錄了簽名演算法和token 的型別。

Payload是以明文儲存的一些資訊,包括使用者自定義資訊。

Signature是使用簽名演算法,對Payload結合服務端才知道的私鑰進行簽名後得出的結果。

服務端對這3部分使用base64編碼,然後以.號分隔,就得到了token字串,格式為:

xxxxxx.yyyyyy.zzzzzz

每次前端進行請求時,帶上這個token字串。當服務端收到請求後,就會對Payload計算簽名,然後與token中的Signature進行比較,若一致,則通過。

邏輯簡單,完全可以自己寫一個token類來實現以上功能。

Spring Boot中提供了一個第三方的token庫,叫jwt。

 

  1. 在pom.xml中引入依賴:
<!-- JWT Json Web Token 依賴 -->
<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.7.0</version>
</dependency>
  1. 生成token:
public String getAccessToken() {
    JwtBuilder jwtBuilder = Jwts.builder();

    long nowMillis = System.currentTimeMillis();
    // 7個官方Payload欄位
    jwtBuilder.setId("1.0"); // 編號/版本
    jwtBuilder.setIssuer("ISSUER"); // 發行人
    jwtBuilder.setSubject("SUBJECT"); // 主題
    jwtBuilder.setAudience("AUDIENCE"); // 受眾
    jwtBuilder.setIssuedAt(new Date(nowMillis)); // 簽發時間
    jwtBuilder.setNotBefore(new Date(nowMillis)); // 生效時間
    jwtBuilder.setExpiration(new Date(nowMillis + (60 * 60 * 1000))); // 失效時間

    // 使用者自定義欄位
    jwtBuilder.claim("id", "AXDBDCD");
    jwtBuilder.claim("name", "testname");
    jwtBuilder.claim("value", "123456");
    
    // 定義私鑰
    String HS256KEY = "xxxxxx";
    // 簽名演算法
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    // 計算簽名key值
    Key signingKey = new SecretKeySpec(Base64.decodeBase64(HS256KEY), signatureAlgorithm.getJcaName());

    // 進行簽名
    jwtBuilder.signWith(signatureAlgorithm, signingKey);

    // 獲取token字串
    String tokenString = jwtBuilder.compact();

    return tokenString;
}

注意:

  • 要先為jwtBuilder設定claims,然後再呼叫jwtBuilderset介面來設定7個官方payload欄位。這是因為jwtBuilder是將7payload欄位儲存在claims中的。若先設定了payload,再設定claims,會將已設定的payload覆蓋掉。
  • payload欄位中:id通常設為版本號;issuer是發行人,通常設為公司名;subject是主題,通常設為工程名;audience是受眾,通常設為獲取token 的介面名。一般來說,audience多被設為” login”,也就是登入。
  • 7個官方payload欄位中的3個時間:簽發時間(issued at),生效時間(not before),失效時間(expiration)。3個時間使用的單位是秒。呼叫jwtBuilderset介面來設定這3個時間時,只需要傳入Date()型資料即可,JwbBuilder會自動將其轉換為秒儲存在claims的相應欄位裡。詳見附錄。
  1. 解析token:
public boolean isTokenValid(String token) {
    try {
        Claims claims = Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token.trim()).getBody();
        Date date = claims.getExpiration();
        return new Date().before(date);
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

將token字串解析為Jws物件,然後獲取claims,這樣就能拿到claims中儲存的各個欄位。然後自行判斷即可。其中解析用的signingKey與2中生成的signingKey是同一個。

由於(當前解析的時間<生效時間)或(當前解析的時間>失效時間)均會丟擲異常,所以需要對異常進行捕獲,返回false給前端,表示token已失效。

 

 

附錄

  1. claims的設定

JwtBuilder允許使用者自定義claims,並提供了3個相關函式:

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

其中:

  • 兩個setClaims會將之前已存在的claims覆蓋掉。
  • claim會將新的值push給當前claims。

JwtBuilder將7個官方payload欄位也儲存在claims中。也就是說,使用者自定義的欄位和官方的payload欄位是存放在一起的。

因此,若已經為JwtBuilder設定了claims欄位,則不能再呼叫setClaims(),否則已設定的會被覆蓋。推薦使用claim()函式。

JwtBuilder提供了7個set函式來設定7個官方payload欄位。以setExpiration()為例。

·若還沒有呼叫JwtBuilder的setClaims(),則呼叫setExpiration(),會先為JwtBuilder新增一個空的claims,然後將expiration設定進去。

·若已經呼叫了JwtBuilder的setClaims(),則呼叫setExpiration(),會將expiration的值push到已存在的claims中。

  1. not before與expiration

7個官方payload欄位中,有3個時間:

·issue at:簽發時間

·not before:生效時間

·expiration:失效時間

當Jwts對token字串進行解析時,若存在not before和expiration,則會進行判斷:

當前解析的時間必須>=not before

當前解析的時間必須<=expiration

否則會丟擲異常。所以解析時需要對異常進行捕獲處理。

若這2個時間都沒有進行設定,則不會進行判斷。

然而,其流程是:

JwtBuilder呼叫setNotBefore()來設定時間→生成token返回給使用者→使用者請求→對請求的token進行解析。

其中:

  • setNotBefore()來設定時間時,將時間轉換為秒,是一個long型的資料,儲存到claims中。
  • 解析時,將claims中的秒取出來,轉成Date型資料,然後與當前時間的Date比較,來判斷是否丟擲異常。

因此:

jwtBuilder.setNotBefore(new Date());
jwtBuilder.claim("nbf", new Date().getTime()/1000);

效果是相同的。

其中7個欄位名(例如"nbf")定義在Claims介面中(例如Claims.NOT_BEFORE)。

 

相關文章