前端实现无感刷新 Token 的核心机制是通过拦截器、双 Token 机制和异步队列管理实现用户无感知的权限续期,具体步骤如下:
一、核心实现机制
-
双 Token 设计
- Access Token:用于接口鉴权,有效期较短(如 2 小时)68
- Refresh Token:用于刷新 Access Token,有效期较长(如 7 天)56
-
拦截器控制流程
- 请求拦截器:在请求头中自动附加当前 Access Token15
- 响应拦截器:捕获 401 状态码(Token 失效),触发刷新流程34
二、实现步骤
1. 请求拦截器:Token 预检与注入
axios.interceptors.request.use(config => {
const accessToken = localStorage.getItem('access_token');
if (accessToken && !isTokenExpired(accessToken)) { // 检查 Token 是否过期:ml-citation{ref="1,2" data="citationList"}
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
2. 响应拦截器:处理 Token 失效
axios.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) { // 捕获 Token 失效状态码:ml-citation{ref="4,7" data="citationList"}
return handleTokenRefresh(error); // 进入刷新流程
}
return Promise.reject(error);
}
);
3. 刷新 Token 核心逻辑
let isRefreshing = false; // 标记是否正在刷新
let requestQueue = []; // 待重试请求队列
const handleTokenRefresh = async (error) => {
const { config } = error;
if (!isRefreshing) {
isRefreshing = true;
try {
const newToken = await axios.post('/refresh', {
refreshToken: localStorage.getItem('refresh_token')
}); // 使用 Refresh Token 获取新 Access Token:ml-citation{ref="6,8" data="citationList"}
localStorage.setItem('access_token', newToken);
// 重试队列中的请求
requestQueue.forEach(cb => cb(newToken));
requestQueue = [];
return axios(config); // 重试原请求:ml-citation{ref="5,7" data="citationList"}
} catch (e) {
// 刷新失败则跳转登录页:ml-citation{ref="7,8" data="citationList"}
window.location.href = '/login';
} finally {
isRefreshing = false;
}
} else {
// 将待重试请求加入队列
return new Promise(resolve => {
requestQueue.push(token => {
config.headers.Authorization = `Bearer ${token}`;
resolve(axios(config));
});
});
}
};
三、安全优化建议
-
存储策略
- 推荐使用
HttpOnly
+Secure
的 Cookie 存储 Refresh Token,降低 XSS 风险68 - Access Token 可存于内存或 localStorage(需配合加密)56
- 推荐使用
-
Token 安全增强
- 使用非对称加密(如 RSA)签名 JWT,防止篡改68
- 限制 Refresh Token 使用频率(如 1 次/小时)56
四、流程示意图
用户发起请求 → 请求拦截器注入 Token → 服务端返回 401
↓
触发响应拦截器 → 检查刷新状态
├─ 未刷新 → 发起 Refresh Token 请求 → 更新本地 Token → 重试原始请求:ml-citation{ref="3,5" data="citationList"}
└─ 已刷新 → 加入队列等待新 Token → 批量重试队列请求:ml-citation{ref="5,7" data="citationList"}