目录

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

前端无感刷新 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:


// 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:


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;