如何实现无感刷新Token

无感刷新 Token 需要前后端配合实现

流程如下

  • 用户登录成功后,接口返回 accessTokenrefrshToken。前端将这两个token存在本地。
  • 后续业务接口请求时,需要在请求头上加上accessToken
  • 当accessToken过期时,调接口(带上refreshToken参数)重新获取 accessToken,然后将失败的、暂停的请求带上最新的accessToken重新处理一遍(队列管理这些请求)
  • 如果刷新 Token 失败,则跳转到登录页重新登录

伪代码如下

js 复制代码
import axios from 'axios';

// 创建一个新的axios实例
const api = axios.create({
  baseURL: '/api',
  timeout: 5000,
});

// ------------------- 请求拦截器 -------------------
api.interceptors.request.use(config => {
  const accessToken = localStorage.getItem('accessToken');
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});


// ------------------- 响应拦截器 -------------------

// 用于标记是否正在刷新token
let isRefreshing = false;
// 用于存储因为token过期而被挂起的请求
let requestsQueue = [];

api.interceptors.response.use(
  response => {
    return response;
  }, 
  async error => {
    const { config, response } = error;
    
    // 如果返回的HTTP状态码是401,说明access_token过期了
    if (response && response.status === 401) {
      
      // 如果当前没有在刷新token,那么我们就去刷新token
      if (!isRefreshing) {
        isRefreshing = true;

        try {
          // 调用刷新token的接口
          const { data } = await axios.post('/refresh-token', {
            refreshToken: localStorage.getItem('refreshToken') 
          });
          
          const newAccessToken = data.accessToken;
          localStorage.setItem('accessToken', newAccessToken);

          // token刷新成功后,重新执行所有被挂起的请求
          requestsQueue.forEach(cb => cb(newAccessToken));
          // 清空队列
          requestsQueue = [];

          // 把本次失败的请求也重新执行一次
          config.headers.Authorization = `Bearer ${newAccessToken}`;
          return api(config);

        } catch (refreshError) {
          // 如果刷新token也失败了,说明refreshToken也过期了
          // 此时只能清空本地存储,跳转到登录页
          console.error('Refresh token failed:', refreshError);
          localStorage.removeItem('accessToken');
          localStorage.removeItem('refreshToken');
          // window.location.href = '/login';
          return Promise.reject(refreshError);
        } finally {
          isRefreshing = false;
        }
      } else {
        // 如果当前正在刷新token,就把这次失败的请求,存储到队列里
        // 返回一个pending的Promise,等token刷新后再去执行
        return new Promise((resolve) => {
          requestsQueue.push((newAccessToken) => {
            config.headers.Authorization = `Bearer ${newAccessToken}`;
            resolve(api(config));
          });
        });
      }
    }
    
    return Promise.reject(error);
  }
);

export default api;

代码里面有两个地方需要注意:

  • isRefreshing 状态锁:

    这是为了解决并发问题。想象一下,如果一个页面同时发起了多个API请求,而accessToken刚好过期,多个请求会同时收到401。如果没有isRefreshing这个锁,它们会同时去调用/refresh-token接口,发起多次刷新请求,这是完全没有必要的浪费,甚至可能因为并发问题导致后端逻辑出错。

    有了这个锁,只有第一个收到401的请求,会真正去执行刷新逻辑。

  • requestsQueue 请求队列:

    比如现在有3个请求,第1个请求正在刷新Token(isRefreshing = true),后面那2个请求把它们的resolve函数推进一个队列(requestsQueue)里,暂时挂起。

    等第一个请求成功拿到新的accessToken后,再遍历这个队列,把所有被挂起的请求,用新的Token重新执行一遍。

相关推荐
用户4099322502122 小时前
Vue3响应式系统的底层原理与实践要点你真的懂吗?
前端·ai编程·trae
qq_479875432 小时前
RVO和移动语义
前端·算法
加菲喵2 小时前
深度解析:在vue3中使用自定义Hooks
前端
hxmmm2 小时前
js中生成器和迭代器
前端
黄交大彭于晏3 小时前
UniApp 全局通知功能实现
前端·vue.js·uni-app
sTone873753 小时前
Android核心概念(一)minSdkVersion targetSdkVersion compileSdkVersion
android·前端
林太白3 小时前
八大数据结构
前端·后端·算法
一 乐3 小时前
流浪动物救助|流浪猫狗救助|基于Springboot+vue的流浪猫狗救助平台设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设
火星数据-Tina3 小时前
Python + WebSocket 实现实时体育比分系统(含数据库设计与前端演示)
开发语言·前端