前端无感刷新 Token 的 Axios 封装方案
在现代前端应用中,基于 Token 的身份验证已成为主流方案。然而,Token 过期问题常常困扰开发者 —— 如何在不打断用户操作的情况下自动刷新 Token,实现 "无感刷新" 体验?本文将详细介绍基于 Axios 的解决方案。
什么是无感刷新 Token?
无感刷新 Token 指的是当用户访问需要身份验证的接口时,若当前 Token 已过期,系统自动使用刷新 Token 获取新的访问 Token,并用新 Token 重新发起原请求,整个过程对用户完全透明,不影响用户操作流程。
实现思路
- 拦截所有请求,在请求头中自动添加 Token
- 拦截响应,检测 Token 过期错误
- 当 Token 过期时,使用刷新 Token 获取新的访问 Token
- 用新 Token 重新发起原请求,并将结果返回给用户
- 处理并发请求问题,避免多次刷新 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, // 基础URLtimeout: 5000 // 请求超时时间
});// 是否正在刷新的标记
let isRefreshing = false;
// 存储等待刷新的请求队列
let requests = [];// 请求拦截器
service.interceptors.request.use(config => {// 从本地存储获取Tokenconst 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);}}// 标记为正在刷新TokenoriginalRequest._retry = true;isRefreshing = true;try {// 调用刷新Token接口const refreshToken = getRefreshToken();const { data } = await axios.post(`${process.env.VUE_APP_BASE_API}/refresh-token`, {refreshToken});// 存储新的TokensetToken(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;