在现代前端应用中,基于 Token 的身份验证已成为主流方案。然而,Token 过期问题常常困扰开发者 ------ 如何在不打断用户操作的情况下自动刷新 Token,实现 "无感刷新" 体验?本文将详细介绍基于 Axios 的解决方案。
什么是无感刷新 Token?
无感刷新 Token 指的是当用户访问需要身份验证的接口时,若当前 Token 已过期,系统自动使用刷新 Token 获取新的访问 Token,并用新 Token 重新发起原请求,整个过程对用户完全透明,不影响用户操作流程。
实现思路
- 拦截所有请求,在请求头中自动添加 Token
- 拦截响应,检测 Token 过期错误
- 当 Token 过期时,使用刷新 Token 获取新的访问 Token
- 用新 Token 重新发起原请求,并将结果返回给用户
- 处理并发请求问题,避免多次刷新 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;