Java 后端實(shí)現(xiàn) token自動(dòng)續(xù)期,這方案有點(diǎn)優(yōu)雅!
后續(xù)每次請(qǐng)求都會(huì)將此token放在請(qǐng)求頭中傳遞到后端服務(wù),后端服務(wù)會(huì)有一個(gè)過(guò)濾器對(duì)token進(jìn)行攔截校驗(yàn),校驗(yàn)token的合法性以及token是否過(guò)期,如果token過(guò)期則會(huì)讓前端跳轉(zhuǎn)到登錄頁(yè)面重新登錄。
因?yàn)閠oken中一般會(huì)包含用戶的基礎(chǔ)信息,為了保證token的安全性,一般會(huì)將token的過(guò)期時(shí)間設(shè)置的比較短。
但是這樣又會(huì)導(dǎo)致前端用戶需要頻繁登錄(token過(guò)期),甚至有的表單比較復(fù)雜,前端用戶在填寫(xiě)表單時(shí)需要思考較長(zhǎng)時(shí)間,等真正提交表單時(shí)后端校驗(yàn)發(fā)現(xiàn)token過(guò)期失效了不得不跳轉(zhuǎn)到登錄頁(yè)面。重新登錄填寫(xiě)后再提交表單,用戶體驗(yàn)非常不友好。
本篇文章的內(nèi)容就要是在前端用戶無(wú)感知的情況下實(shí)現(xiàn)token的自動(dòng)續(xù)期,避免頻繁登錄、表單填寫(xiě)內(nèi)容丟失情況的發(fā)生。當(dāng)然,這只是萬(wàn)千解決方案中的一種,如果你有更好的方案,歡迎留言評(píng)論。
實(shí)現(xiàn)原理
token自動(dòng)續(xù)期的實(shí)現(xiàn)原理如下:
登錄成功后將用戶生成的 token作為key、value存儲(chǔ)到cache緩存里面 (這時(shí)候key、value值一樣),將緩存有效期設(shè)置為 token有效時(shí)間的2倍。當(dāng)該用戶再次請(qǐng)求時(shí),通過(guò)后端的一個(gè) Filter校驗(yàn)前端token是否是有效token,如果token無(wú)效表明是非法請(qǐng)求,直接拋出異常即可;根據(jù)規(guī)則從cache緩存中取出token,判斷 cache token是否存在,此時(shí)有以下幾種情況:cache token不存在 這種情況說(shuō)明token在緩存中過(guò)期了,表明該用戶賬戶空閑時(shí)間過(guò)長(zhǎng),此時(shí)屬于正常過(guò)期,后端直接返回用戶信息已失效,請(qǐng)重新登錄即可。cache token存在,則需要使用jwt工具類驗(yàn)證該cache token 是否過(guò)期超時(shí),不過(guò)期無(wú)需處理。過(guò)期則表示該用戶一直在操作只是token失效了,后端程序會(huì)給token對(duì)應(yīng)的key映射的value值重新生成 token并覆蓋value值,該緩存生命周期重新計(jì)算。
實(shí)現(xiàn)邏輯的核心原理:
前端請(qǐng)求Header中設(shè)置的token保持不變,校驗(yàn)有效性以緩存中的token為準(zhǔn)。
代碼實(shí)現(xiàn)(偽碼)
登錄成功后給用戶簽發(fā)token,并設(shè)置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)?{
????//設(shè)置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,并設(shè)定過(guò)期時(shí)間,將redis的過(guò)期時(shí)間設(shè)置成token過(guò)期時(shí)間的兩倍
Sting?tokenKey?=?"sys:user:token"?+?token;
redisUtil.set(tokenKey,?token);
redisUtil.expire(tokenKey,?30?*?60?*?2);
//將token返回給前端用戶
return?token;
前端調(diào)用后端接口時(shí)在請(qǐng)求頭中添加token(略)
過(guò)濾器校驗(yàn)token,校驗(yàn)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);
?//根據(jù)用戶名獲取用戶實(shí)體,在實(shí)際開(kāi)發(fā)中從redis取
?User?user?=?userService.findByUser(username);
????if(null?==?user){
??throw?new?RuntimeException("illegal?request,token?is?Invalid!")
????}
?//校驗(yàn)token是否失效,自動(dòng)續(xù)期
?if(!refreshToken(token,username,user.getPassword())){
??throw?new?RuntimeException("illegal?request,token?is?expired!")
?}
?...
}
實(shí)現(xiàn)token的自動(dòng)續(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))?{
??//?校驗(yàn)token有效性,注意需要校驗(yàn)的是緩存中的token
??if?(!JwtUtil.verify(cacheToken,?userName,?passWord))?{
???String?newToken?=?JwtUtil.sign(userName,?passWord);
???//?設(shè)置超時(shí)時(shí)間
???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?{
??//?根據(jù)密碼生成JWT效驗(yàn)器
??Algorithm?algorithm?=?Algorithm.HMAC256(secret);
??JWTVerifier?verifier?=?JWT.require(algorithm).withClaim("username",?username).build();
??//?效驗(yàn)TOKEN
??DecodedJWT?jwt?=?verifier.verify(token);
??return?true;
?}?catch?(Exception?exception)?{
??return?false;
?}
}
本文中jwt的相關(guān)操作是基于 com.auth0.java-jwt 實(shí)現(xiàn),大家可以通過(guò)閱讀原文獲取 JWTUtil 工具類。
小結(jié)
jwt token實(shí)現(xiàn)邏輯的核心原理是 前端請(qǐng)求Header中設(shè)置的token保持不變,校驗(yàn)有效性以緩存中的token為準(zhǔn),千萬(wàn)不要直接校驗(yàn)Header中的token。實(shí)現(xiàn)原理部分大家好好體會(huì)一下,思路比實(shí)現(xiàn)更重要!
在看、點(diǎn)贊、轉(zhuǎn)發(fā),是對(duì)我最大的鼓勵(lì)。

