1. 程式人生 > 其它 >淺析JWT中token過期後解析報錯ExpiredJwtException的解決及過期之後如何進行後續業務處理

淺析JWT中token過期後解析報錯ExpiredJwtException的解決及過期之後如何進行後續業務處理

一、問題背景

  最近搭建springcloud的專案,專案採取了Jwt + spring security 來進行登入驗證,Jwt token 鎖定使用者的失效時間,但是由於 jwt token特性導致token失效時間無法重新整理,所以必須新建立一個token令牌,用來代替之前已失效token。

  (token失效時間無法重新整理的原因是由於jwt建立token是根據jwt儲存的相關資訊來計算的,過期時間是其中的一個計算維度,所以一旦過期時間改了,那麼生成的token值也就變了。)

  之後為了解決這個問題,結合了redis,將token值儲存到redis中,使用者操作後重新整理redis的有效時間,這樣如果jwt token失效了,再檢查 redis 中儲存token的key是否失效,如果沒有失效,那麼就重新建立jwt token ,失效了,就重新登入。

  儲存在redis中的 key 是使用者名稱, 但是我需要把 jwt token 轉化後從 claims 中取出這個使用者名稱,一開始我直接轉化,進行debug的時候發現如果token超時了,jwt 沒有返回轉化結果, 而是直接丟擲了異常,我檢視JWT所有的轉化方法,發現Jwt所有的轉化最終處理都是parse(claimJws)這個方法,而這個方法正是我一開始用的解析方法。

  原本是呼叫jwtUtil(jwt的工具類),傳入一個token,判斷是否過期,然而卻莫名其妙得拋異常了,而業務中還需要根據是否過期進行後續邏輯!異常如下:

io.jsonwebtoken.ExpiredJwtException: JWT expired at 2020-07-29T14:48:14Z. Current time: 2020-07-29T14:48:50Z, a difference of 36843 milliseconds.  Allowed clock skew: 0 milliseconds.
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:
385) at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481) at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541) at com.smart.util.JwtUtil.parseJwt(JwtUtil.java:63) at com.smart.util.JwtUtil.isTokenExpired(JwtUtil.java:93)

二、問題原因

  根據報錯堆疊資訊找到了DefaultJwtParser類中,找到了問題的原因。

boolean allowSkew = this.allowedClockSkewMillis > 0L;
if (claims != null) {
    Date now = this.clock.now();
    long nowTime = now.getTime();
    Date exp = claims.getExpiration();
    String nbfVal;
    SimpleDateFormat sdf;
    if (exp != null) {
        long maxTime = nowTime - this.allowedClockSkewMillis;
        Date max = allowSkew ? new Date(maxTime) : now;
        if (max.after(exp)) {
            sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            String expVal = sdf.format(exp);
            nbfVal = sdf.format(now);
            long differenceMillis = maxTime - exp.getTime();
            String msg = "JWT expired at " + expVal + ". Current time: " + nbfVal + ", a difference of " + differenceMillis + " milliseconds.  Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
            throw new ExpiredJwtException((Header)header, claims, msg);
        }
    }
    ......
}

  看到結尾的throw new ExpiredJwtException,我相信就找到了問題的關鍵,原來在在解析token並發現這個token已經過期了,它作出的反應是直接拋異常。

  異常定義的構造方法中除了msg資訊,還有claims和header資訊。

  檢查claims發現,在異常之前token其實已經解析完畢。

  這樣也就代表著,丟擲的這個異常 ExpiredJwtException 中有一個引數 claims 就是解析後的token,那麼本次這個問題也就解決了。

  catchExpiredJwtException 異常後,直接從異常中獲取解析的資料即可,如下介紹。

三、過期報錯了,如何進行後續業務處理

  回到我們的工具類中的解析jwt的方法:

public Claims parseJwt(String token){
    Claims claims = Jwts.parser()
                .setSigningKey(signKey) // 設定標識名
                .parseClaimsJws(token)  //解析token
                .getBody();
    return claims;
}

  改為:不管是否過期都返回 claims 物件

//不管是否過期,都返回claims物件
public Claims parseJwt(String token){
    Claims claims;
    try {
        claims = Jwts.parser()
                .setSigningKey(signKey) // 設定標識名
                .parseClaimsJws(token)  //解析token
                .getBody();
    } catch (ExpiredJwtException e) {
        claims = e.getClaims();
    }
    return claims;
}

  可以從異常中找到這個過期的claim物件資訊;判斷token是否過期的方法我也相應修改了,如下:

public Boolean isTokenExpired(String token) {
  //不管是否過期,都返回claims物件
  Claims claims = this.parseJwt(token);
  Date expiration = claims.getExpiration();
  //和當前時間進行對比來判斷是否過期
  return new Date(System.currentTimeMillis()).after(expiration);
}

參考文章:

https://blog.csdn.net/qq_38294335/article/details/107669630

https://blog.csdn.net/Piteover/article/details/90676279