前端无感刷新 Token 的 Axios 封装方案

在现代前端应用中,基于 Token 的身份验证已成为主流方案。然而,Token 过期问题常常困扰开发者 ------ 如何在不打断用户操作的情况下自动刷新 Token,实现 "无感刷新" 体验?本文将详细介绍基于 Axios 的解决方案。

什么是无感刷新 Token?

无感刷新 Token 指的是当用户访问需要身份验证的接口时,若当前 Token 已过期,系统自动使用刷新 Token 获取新的访问 Token,并用新 Token 重新发起原请求,整个过程对用户完全透明,不影响用户操作流程。

实现思路
  1. 拦截所有请求,在请求头中自动添加 Token
  2. 拦截响应,检测 Token 过期错误
  3. 当 Token 过期时,使用刷新 Token 获取新的访问 Token
  4. 用新 Token 重新发起原请求,并将结果返回给用户
  5. 处理并发请求问题,避免多次刷新 Token

代码

auth.js:

javascript 复制代码
// Token存储工具函数

// 存储Token到本地存储
export const setToken = (token) => {
  localStorage.setItem('accessToken', token);
};

// 从本地存储获取Token
export const getToken = () => {
  return localStorage.getItem('accessToken');
};

// 存储刷新Token到本地存储
export const setRefreshToken = (refreshToken) => {
  localStorage.setItem('refreshToken', refreshToken);
};

// 从本地存储获取刷新Token
export const getRefreshToken = () => {
  return localStorage.getItem('refreshToken');
};

// 清除所有Token
export const removeTokens = () => {
  localStorage.removeItem('accessToken');
  localStorage.removeItem('refreshToken');
};

request.js:

javascript 复制代码
import axios from 'axios';
import { getToken, getRefreshToken, setToken, removeTokens } from './auth';

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // 基础URL
  timeout: 5000 // 请求超时时间
});

// 是否正在刷新的标记
let isRefreshing = false;
// 存储等待刷新的请求队列
let requests = [];

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 从本地存储获取Token
    const token = getToken();
    // 如果Token存在,则添加到请求头
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    // 请求错误处理
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 成功响应处理
    return response.data;
  },
  async error => {
    const originalRequest = error.config;
    
    // 如果不是401错误或者已经重试过,则直接返回错误
    if (error.response?.status !== 401 || originalRequest._retry) {
      return Promise.reject(error);
    }
    
    // 如果正在刷新Token,则将请求加入队列
    if (isRefreshing) {
      try {
        // 等待刷新Token完成
        const token = await new Promise(resolve => {
          requests.push(token => {
            resolve(token);
          });
        });
        // 使用新Token重新发起请求
        originalRequest.headers['Authorization'] = `Bearer ${token}`;
        return service(originalRequest);
      } catch (err) {
        return Promise.reject(err);
      }
    }
    
    // 标记为正在刷新Token
    originalRequest._retry = true;
    isRefreshing = true;
    
    try {
      // 调用刷新Token接口
      const refreshToken = getRefreshToken();
      const { data } = await axios.post(`${process.env.VUE_APP_BASE_API}/refresh-token`, {
        refreshToken
      });
      
      // 存储新的Token
      setToken(data.token);
      
      // 执行队列中的请求
      requests.forEach(cb => cb(data.token));
      requests = [];
      
      // 重新发起原请求
      originalRequest.headers['Authorization'] = `Bearer ${data.token}`;
      return service(originalRequest);
    } catch (refreshError) {
      // 刷新Token失败,清除Token并跳转登录页
      removeTokens();
      window.location.href = '/login';
      return Promise.reject(refreshError);
    } finally {
      // 重置刷新状态
      isRefreshing = false;
    }
  }
);

export default service;
相关推荐
a1117761 天前
医院挂号预约系统(开源 Fastapi+vue2)
前端·vue.js·python·html5·fastapi
0思必得01 天前
[Web自动化] Selenium处理iframe和frame
前端·爬虫·python·selenium·自动化·web自动化
行走的陀螺仪1 天前
uni-app + Vue3编辑页/新增页面给列表页传参
前端·vue.js·uni-app
We་ct1 天前
LeetCode 205. 同构字符串:解题思路+代码优化全解析
前端·算法·leetcode·typescript
2301_812731411 天前
CSS3笔记
前端·笔记·css3
ziblog1 天前
CSS3白云飘动动画特效
前端·css·css3
越努力越幸运5081 天前
CSS3学习之网格布局grid
前端·学习·css3
半斤鸡胗1 天前
css3基础
前端·css
ziblog1 天前
CSS3创意精美页面过渡动画效果
前端·css·css3
akangznl1 天前
第四章 初识css3
前端·css·css3·html5