前言
在前後端分離的開發模式下,前端使用者登入成功後後端服務會給使用者頒發一個token。前端(如
vue)在接收到 token後會将token存儲到LocalStorage中。
後續每次請求都會将此token放在請求頭中傳遞到後端服務,後端服務會有一個過濾器對token進行攔截校驗,校驗token的合法性以及token是否過期,如果token過期則會讓前端跳轉到登入頁面重新登入。
因為token中一般會包含使用者的基礎資訊,為了保證token的安全性,一般會将token的過期時間設定的比較短。
但是這樣又會導緻前端使用者需要頻繁登入(token過期),甚至有的表單比較複雜,前端使用者在填寫表單時需要思考較長時間,等真正送出表單時後端校驗發現token過期失效了不得不跳轉到登入頁面。重新登入填寫後再送出表單,使用者體驗非常不友好。
本篇文章的内容就要是在前端使用者無感覺的情況下實作token的自動續期,避免頻繁登入、表單填寫内容丢失情況的發生。當然,這隻是萬千解決方案中的一種,如果你要更好的方案,歡迎留言評論。
實作原理
token自動續期的實作原理如下:
- 登入成功後将使用者生成的
作為key、value存儲到cache緩存裡面 (這時候key、value值一樣),将緩存有效期設定為 token有效時間的2倍。token
- 當該使用者再次請求時,通過後端的一個
校驗前端token是否是有效token,如果token無效表明是非法請求,直接抛出異常即可;Filter
- 根據規則從cache緩存中取出token,判斷
是否存在,此時有以下幾種情況:cache token
-
-
cache token
不存在
這種情況說明token在緩存中過期了,表明該使用者賬戶空閑時間過長,此時屬于正常過期,後端直接傳回使用者資訊已失效,請重新登入即可。
-
-
-
存在,則需要使用jwt工具類驗證該cache token 是否過期逾時,不過期無需處理。過期則表示該使用者一直在操作隻是token失效了,後端程式會給token對應的key映射的value值重新生成 token并覆寫value值,該緩存生命周期重新計算。cache token
-
實作邏輯的核心原理:
前端請求Header中設定的token保持不變,校驗有效性以緩存中的token為準。
代碼實作(僞碼)
- 登入成功後給使用者簽發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傳回給前端使用者
return token;
- 前端調用後端接口時在請求頭中添加token(略)
- 過濾器校驗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);
//根據使用者名擷取使用者實體,在實際開發中從redis取
User user = userService.findByUser(username);
if(null == user){
throw new RuntimeException("illegal request,token is Invalid!")
}
//校驗token是否失效,自動續期
if(!refreshToken(token,username,user.getPassword())){
throw new RuntimeException("illegal request,token is expired!")
}
...
}
- 實作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);
//根據使用者名擷取使用者實體,在實際開發中從redis取
User user = userService.findByUser(username);
if(null == user){
throw new RuntimeException("illegal request,token is Invalid!")
}
//校驗token是否失效,自動續期
if(!refreshToken(token,username,user.getPassword())){
throw new RuntimeException("illegal request,token is expired!")
}
...
}
本文中jwt的相關操作是基于
com.auth0.java-jwt
實作,大家可以通過關注下方卡片擷取。
小結
jwt token實作邏輯的核心原理是 前端請求Header中設定的token保持不變,校驗有效性以緩存中的token為準,千萬不要直接校驗Header中的token。實作原理部分大家好好體會一下,思路比實作更重要!