天天看點

java後端實作token自動續期,這方案有點優雅

前言

在前後端分離的開發模式下,前端使用者登入成功後後端服務會給使用者頒發一個token。前端(如

vue

)在接收到 token後會将token存儲到LocalStorage中。

後續每次請求都會将此token放在請求頭中傳遞到後端服務,後端服務會有一個過濾器對token進行攔截校驗,校驗token的合法性以及token是否過期,如果token過期則會讓前端跳轉到登入頁面重新登入。

因為token中一般會包含使用者的基礎資訊,為了保證token的安全性,一般會将token的過期時間設定的比較短。

但是這樣又會導緻前端使用者需要頻繁登入(token過期),甚至有的表單比較複雜,前端使用者在填寫表單時需要思考較長時間,等真正送出表單時後端校驗發現token過期失效了不得不跳轉到登入頁面。重新登入填寫後再送出表單,使用者體驗非常不友好。

本篇文章的内容就要是在前端使用者無感覺的情況下實作token的自動續期,避免頻繁登入、表單填寫内容丢失情況的發生。當然,這隻是萬千解決方案中的一種,如果你要更好的方案,歡迎留言評論。

實作原理

token自動續期的實作原理如下:

  1. 登入成功後将使用者生成的

    token

    作為key、value存儲到cache緩存裡面 (這時候key、value值一樣),将緩存有效期設定為 token有效時間的2倍。
  1. 當該使用者再次請求時,通過後端的一個

    Filter

    校驗前端token是否是有效token,如果token無效表明是非法請求,直接抛出異常即可;
  1. 根據規則從cache緩存中取出token,判斷

    cache token

    是否存在,此時有以下幾種情況:
    • cache token

      不存在

      這種情況說明token在緩存中過期了,表明該使用者賬戶空閑時間過長,此時屬于正常過期,後端直接傳回使用者資訊已失效,請重新登入即可。

    • cache token

      存在,則需要使用jwt工具類驗證該cache token 是否過期逾時,不過期無需處理。過期則表示該使用者一直在操作隻是token失效了,後端程式會給token對應的key映射的value值重新生成 token并覆寫value值,該緩存生命周期重新計算。

實作邏輯的核心原理:

前端請求Header中設定的token保持不變,校驗有效性以緩存中的token為準。

代碼實作(僞碼)

  1. 登入成功後給使用者簽發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);
}      
  1. 将token存入redis,并設定過期時間,将redis的過期時間設定成token過期時間的兩倍
Sting tokenKey = "sys:user:token" + token;
redisUtil.set(tokenKey, token);
redisUtil.expire(tokenKey, 30 * 60 * 2);

//将token傳回給前端使用者
return token;      
  1. 前端調用後端接口時在請求頭中添加token(略)
  1. 過濾器校驗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!")
    }
    ...
}      
  1. 實作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。實作原理部分大家好好體會一下,思路比實作更重要!