前端无感刷新 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;
相关推荐
不会敲代码120 小时前
TCP/IP 与前端性能:从数据包到首次渲染的底层逻辑
前端·tcp/ip
kyriewen20 小时前
奥特曼借GPT-5.5干杯,而你的Copilot正按Token收钱
前端·github·openai
AC赳赳老秦20 小时前
投标合规提效:用 OpenClaw 实现标书 / 合同自动审核、关键词校验、格式优化,降低废标风险
开发语言·前端·python·eclipse·emacs·deepseek·openclaw
kyriewen20 小时前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
前端·javascript·设计模式
千寻girling21 小时前
《 Git 详细教程 》
前端·后端·面试
之歆1 天前
DAY08_CSS浮动与行内块布局实战指南(下)
前端·css
yqcoder1 天前
CSS Position 全解析:5 种定位模式详解
前端·css
Rhi6371 天前
从零搭建项目:React 19 + Vite 8 + Tailwind CSS v4 实战配置
前端
竹林8181 天前
用Viem替代ethers.js:从一次签名失败到完整迁移的实战记录
前端·javascript
之歆1 天前
DAY08_CSS浮动与行内块布局实战指南(上)
前端·css