淺析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