一文看懂无感刷新Token:让用户登录体验更顺畅的秘诀

885 阅读4分钟

无感刷新Token是一种技术手段,能让用户在使用网站或APP时,不用频繁登录,系统自动帮你续命,体验更加流畅。下面用最简单的语言和丰富的代码示例,帮你彻底理解它的原理和实现。

什么是Token?

  • Token是一种“身份凭证”,类似于你进门的通行证。
  • 用户登录后,服务器发给客户端一个Token,客户端每次请求时带上它,服务器用它确认你的身份。
  • Token有有效期,过期后就不能用了。

为什么需要“刷新Token”?

  • Token有效期短是为了安全,比如15分钟。
  • 如果Token过期了,用户就得重新登录,体验很差。
  • 解决办法是用另一个叫Refresh Token的长期凭证,自动帮你换新的短期Token,用户感觉不到。

“无感刷新Token”是啥?

  • 当短期Token(access token)过期时,系统自动用长期Token(refresh token)换一个新的短期Token。
  • 换完后,自动重试之前失败的请求。
  • 用户完全感觉不到,不用重新登录或刷新页面。

形象比喻:地铁票

  • 短期票(access token):有效期15分钟,刷票进站。
  • 长期通行证(refresh token):有效期长,可以换短期票。
  • 票过期时,系统自动帮你换票,你继续刷闸机进站,无感知。
  • 如果长期通行证也过期了,才需要重新买票(登录)。

技术原理和流程

双Token机制

Token类型作用有效期
Access Token请求接口的凭证短(分钟级)
Refresh Token刷新Access Token用长(天或月级)

请求流程

  1. 请求接口时,带上Access Token。

  2. 服务器验证Token:

    • 如果有效,正常返回数据。
    • 如果过期,返回401或特定错误码。
  3. 前端捕获过期状态,自动用Refresh Token请求刷新接口。

  4. 刷新成功,拿到新Access Token,更新本地存储。

  5. 重新发起之前失败的请求。

  6. 如果Refresh Token也过期,跳转登录页。

伪流程图

text
[客户端发请求]
      |
[Access Token有效?]
    /      \
  是        否
  |         |
请求接口  [Refresh Token有效?]
            /          \
          是            否
          |             |
    刷新Access Token    跳转登录
          |
    重新发起请求

关键实现点

1. 前端拦截器自动刷新Token

  • 请求发出前或响应回来后,判断Token是否过期。
  • 过期时发起刷新请求。
  • 刷新期间,其他请求排队等待刷新完成。
  • 刷新成功后,继续执行排队请求。

2. 滑动过期机制

  • Refresh Token存在Redis,设置过期时间(如7天)。
  • 每次刷新时,延长Redis中Refresh Token的过期时间,保持活跃。
  • Redis过期即表示Refresh Token失效。

3. 安全性

  • 不完全信任JWT自带的过期时间,以Redis的过期状态为准。
  • 即使Token被盗,没有Redis配合也无法使用。

代码示例:基于axios的无感刷新Token实现(Vue/React通用)

import axios from 'axios';

// 创建axios实例
const api = axios.create({
  baseURL: '/api',
  headers: { 'Content-Type': 'application/json' }
});

let isRefreshing = false;  // 是否正在刷新Token
let requestQueue = [];     // 等待刷新完成的请求队列

// 获取本地Token
function getAccessToken() {
  return localStorage.getItem('accessToken');
}

function getRefreshToken() {
  return localStorage.getItem('refreshToken');
}

// 保存新Token
function setTokens({ accessToken, refreshToken }) {
  if (accessToken) localStorage.setItem('accessToken', accessToken);
  if (refreshToken) localStorage.setItem('refreshToken', refreshToken);
}

// 刷新Token接口
function refreshToken() {
  return axios.post('/auth/refresh-token', {
    refreshToken: getRefreshToken()
  });
}

// 请求拦截器:每个请求带上Access Token
api.interceptors.request.use(config => {
  const token = getAccessToken();
  if (token) {
    config.headers['Authorization'] = 'Bearer ' + token;
  }
  return config;
});

// 响应拦截器:处理Token过期
api.interceptors.response.use(
  response => response,
  error => {
    const originalRequest = error.config;
    if (error.response && error.response.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // 刷新中,把请求加入队列,等待刷新完成
        return new Promise((resolve) => {
          requestQueue.push(token => {
            originalRequest.headers['Authorization'] = 'Bearer ' + token;
            resolve(api(originalRequest));
          });
        });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      // 发起刷新Token请求
      return refreshToken().then(res => {
        const { accessToken, refreshToken } = res.data;
        setTokens({ accessToken, refreshToken });
        api.defaults.headers.common['Authorization'] = 'Bearer ' + accessToken;

        // 执行队列中的请求
        requestQueue.forEach(cb => cb(accessToken));
        requestQueue = [];
        isRefreshing = false;

        // 重新发起原始请求
        originalRequest.headers['Authorization'] = 'Bearer ' + accessToken;
        return api(originalRequest);
      }).catch(() => {
        isRefreshing = false;
        requestQueue = [];
        // 刷新失败,跳转登录
        window.location.href = '/login';
        return Promise.reject(error);
      });
    }
    return Promise.reject(error);
  }
);

实践中常见的数值指标

  • Access Token有效期:10-30分钟(根据安全需求调整)
  • Refresh Token有效期:7天-30天
  • Redis滑动过期时间:每次刷新时延长7天
  • 请求重试队列长度:根据并发请求量调整,通常几十条即可

总结

  • 无感刷新Token技术让用户登录体验更顺畅,避免频繁登录。
  • 采用短期Access Token和长期Refresh Token双机制,兼顾安全和体验。
  • 通过前端拦截器自动刷新Token,后台滑动过期管理Refresh Token。
  • 代码实现中注意处理并发刷新和请求排队。
  • 安全上依赖Redis控制Token有效性,防止被盗用。

掌握无感刷新Token,是现代前后端分离应用提升用户体验和安全性的关键技术。希望本文的讲解和示例能帮你快速理解和实现这一机制。