如何实现无感刷新Token?

81 阅读2分钟

一、前言

不知道小伙伴们有没有遇到过这么一种情况,你在网站上填一份表格的时候,填了大半天,最后点击提交按钮的时候,突然弹框提示你:"登录状态已过期,请重新登录!"。此时此刻的你,内心肯定很崩溃,其实这个就是Token过期处理不当,导致用户体验很差的问题。我们今天就来聊聊,无感刷新Token是如何实现的,让用户即使在Token过期的情况下,也能无缝地继续操作。

二、令牌设计与生成

首先我们需要两个Token:

  1. accessToken: 这是我们每次请求业务接口时,都需要在header请求头里带上的令牌。它的特点是生命周期短(比如1小时、半小时),因为暴露的风险更高。
  2. refreshToken: 它的唯一作用,就是用来获取一个新的accessTokenrefreshToken。它的特点是生命周期长(比如7天),并且需要被安全地存储。

整体的流程设计:

用户进行登录,登录接口会返回accessTokenrefreshToken这两个Token,前端定时去检查accessToken是否有效,比如每5分钟去检查一次,判断到accessToken还剩5分钟就过期的时候,就调用刷新accessTokenrefreshToken的接口。

下面附上部分示例代码:

/**
 * 刷新令牌(该接口会返回新的accessToken和refreshToken)
 */
@GetMapping("/refresh")
public Tokens getRefreshToken() {
    UserDO user = LocalUser.getLocalUser();
    return jwt.generateTokens(user.getId());
}
public Tokens generateTokens(long identity) {
    String access = this.generateToken("access", identity, this.accessExpire);
    String refresh = this.generateToken("refresh", identity, this.refreshExpire);
    return new Tokens(access, refresh);
}
public class Tokens {
    private String accessToken;
    private String refreshToken;

    public Tokens(String accessToken, String refreshToken) {
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
    }

    public String getAccessToken() {
        return this.accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getRefreshToken() {
        return this.refreshToken;
    }

    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }

    public String toString() {
        return "Tokens{accessToken='" + this.accessToken + '\'' + ", refreshToken='" + this.refreshToken + '\'' + '}';
    }
}

前端代码:

// 定时检查Token有效期
setInterval(() => {
  const token = localStorage.getItem('accessToken');
  if (token && isTokenExpiringSoon(token)) { // 剩余<5分钟
    axios.post('/auth/refresh', {}, { withCredentials: true })
      .then(res => {
        localStorage.setItem('accessToken', res.data.accessToken);
      });
  }
}, 300000); // 每5分钟检查