關于JWT Token 自動續(xù)期的解決方案

前言
在前后端分離的開發(fā)模式下,前端用戶登錄成功后后端服務會給用戶頒發(fā)一個jwt token。前端(如vue)在接收到jwt token后會將token存儲到LocalStorage中。
后續(xù)每次請求都會將此token放在請求頭中傳遞到后端服務,后端服務會有一個過濾器對token進行攔截校驗,校驗token是否過期,如果token過期則會讓前端跳轉到登錄頁面重新登錄。
因為jwt token中一般會包含用戶的基本信息,為了保證token的安全性,一般會將token的過期時間設置的比較短。
但是這樣又會導致前端用戶需要頻繁登錄(token過期),甚至有的表單比較復雜,前端用戶在填寫表單時需要思考較長時間,等真正提交表單時后端校驗發(fā)現token過期失效了不得不跳轉到登錄頁面。
如果真發(fā)生了這種情況前端用戶肯定是要罵人的,用戶體驗非常不友好。本篇內容就是在前端用戶無感知的情況下實現token的自動續(xù)期,避免頻繁登錄、表單填寫內容丟失情況的發(fā)生。
實現原理
jwt token自動續(xù)期的實現原理如下:
登錄成功后將用戶生成的 jwt token作為key、value存儲到cache緩存里面 (這時候key、value值一樣),將緩存有效期設置為 token有效時間的2倍。當該用戶再次請求時,通過后端的一個 jwt Filter校驗前端token是否是有效token,如果前端token無效表明是非法請求,直接拋出異常即可;根據規(guī)則取出cache token,判斷cache token是否存在,此時主要分以下幾種情況: cache token 不存在
這種情況表明該用戶賬戶空閑超時,返回用戶信息已失效,請重新登錄。cache token 存在,則需要使用jwt工具類驗證該cache token 是否過期超時,不過期無需處理。
過期則表示該用戶一直在操作只是token失效了,后端程序會給token對應的key映射的value值重新生成jwt token并覆蓋value值,該緩存生命周期重新計算。
實現邏輯的核心原理:前端請求Header中設置的token保持不變,校驗有效性以緩存中的token為準。
代碼實現(偽碼)
登錄成功后給用戶簽發(fā)token,并設置token的有效期
...
SysUser?sysUser?=?userService.getUser(username,password);
if(null?!==?sysUser){
????String?token?=?JwtUtil.sign(sysUser.getUsername(),?
sysUser.getPassword());
}
...
public?static?String?sign(String?username,?String?secret)?{
????//設置token有效期為30分鐘
?Date?date?=?new?Date(System.currentTimeMillis()?+?30?*?60?*?1000);
?//使用HS256生成token,密鑰則是用戶的密碼
?Algorithm?algorithm?=?Algorithm.HMAC256(secret);
?//?附帶username信息
?return?JWT.create().withClaim("username",?username).withExpiresAt(date).sign(algorithm);
}
將token存入redis,并設定過期時間,將redis的過期時間設置成token過期時間的兩倍
Sting?tokenKey?=?"sys:user:token"?+?token;
redisUtil.set(tokenKey,?token);
redisUtil.expire(tokenKey,?30?*?60?*?2);
過濾器校驗token,校驗token有效性
public?void?doFilter(ServletRequest?req,?ServletResponse?res,?FilterChain?chain)?throws?IOException,?ServletException?{
????//從header中獲取token
?String?token?=?httpServletRequest.getHeader("token")
?if(null?==?token){
??throw?new?RuntimeException("illegal?request,token?is?necessary!")
?}
????//解析token獲取用戶名
?String?username?=?JwtUtil.getUsername(token);
?//根據用戶名獲取用戶實體,在實際開發(fā)中從redis取
?User?user?=?userService.findByUser(username);
????if(null?==?user){
??throw?new?RuntimeException("illegal?request,token?is?Invalid!")
????}
?//校驗token是否失效,自動續(xù)期
?if(!refreshToken(token,username,user.getPassword())){
??throw?new?RuntimeException("illegal?request,token?is?expired!")
?}
?...
}
實現token的自動續(xù)期
public?boolean?refreshToken(String?token,?String?userName,?String?passWord)?{
?Sting?tokenKey?=?"sys:user:token"?+?token?;
?String?cacheToken?=?String.valueOf(redisUtil.get(tokenKey));
?if?(StringUtils.isNotEmpty(cacheToken))?{
??//?校驗token有效性,注意需要校驗的是緩存中的token
??if?(!JwtUtil.verify(cacheToken,?userName,?passWord))?{
???String?newToken?=?JwtUtil.sign(userName,?passWord);
???//?設置超時時間
???redisUtil.set(tokenKey,?newToken)?;
???redisUtil.expire(tokenKey,?30?*?60?*?2);
??}
??return?true;
?}
?return?false;
}
...
public?static?boolean?verify(String?token,?String?username,?String?secret)?{
?try?{
??//?根據密碼生成JWT效驗器
??Algorithm?algorithm?=?Algorithm.HMAC256(secret);
??JWTVerifier?verifier?=?JWT.require(algorithm).withClaim("username",?username).build();
??//?效驗TOKEN
??DecodedJWT?jwt?=?verifier.verify(token);
??return?true;
?}?catch?(Exception?exception)?{
??return?false;
?}
}
本文中jwt的相關操作是基于 com.auth0.java-jwt 實現,大家可以通過閱讀原文獲取 JwtUtil 工具類。
小結
jwt token實現邏輯的核心原理是 前端請求Header中設置的token保持不變,校驗有效性以緩存中的token為準,千萬不要直接校驗Header中的token。實現原理部分大家好好體會一下,思路比實現更重要!
評論
圖片
表情
