现在我们在面对Token过期的时候 经常采用401的方式让用户重新去登录 这样的方式合理 但不够完美
如果能让用户毫无感知的重新登录 用户体验绝对会更好
对此 我专门找后端研究了一下这个方案 并且最后成功在一个项目中实现 下面是基本流程:
第一步:后端配合 —— Refresh Token 存 HttpOnly Cookie
HTTP/1.1 200 OK
Set-Cookie: refreshToken=abc123; HttpOnly; Secure; SameSite=Strict; Path=/auth第二步:前端实现“单例刷新锁 + 请求队列”
let isRefreshing = false;
let refreshPromise = null;
const failedQueue = [];
// 重试队列中的请求
const processQueue = (error, token = null) => {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
} else {
resolve(token);
}
});
failedQueue.length = 0;
};
axios.interceptors.response.use(
response => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// 已在刷新中,将请求加入队列,等待新 token
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
}).then(token => {
originalRequest.headers['Authorization'] = `Bearer ${token}`;
return axios(originalRequest);
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
// 调用刷新接口(后端从 Cookie 读 refreshToken)
const { data } = await axios.post('/auth/refresh');
const newAccessToken = data.accessToken;
// 通知所有排队的请求
processQueue(null, newAccessToken);
// 重试当前请求
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
return axios(originalRequest);
} catch (refreshError) {
// 刷新失败:清空本地身份,跳转登录
clearAuth();
processQueue(refreshError, null);
window.location.href = '/login';
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
refreshPromise = null;
}
}
return Promise.reject(error);
}
);最后进行测试:
手动将 Access Token 设为过期;
快速点击多个按钮,触发并发请求;
观察 Network 面板:
是否只调用了一次
/auth/refresh?所有原请求是否最终成功?
模拟 Refresh Token 失效,是否跳转登录?
完美实现!!