Token过期触发401时,若刷新令牌请求也被同一拦截器捕获,将导致无限重试循环。根本解法是创建纯净的、无响应拦截器的独立axios实例处理刷新请求,并添加_retry标记与URL排除双重防护,刷新失败时彻底清理状态并跳转登录页。
当 token 过期触发 401 响应时,若刷新令牌请求本身也被同一拦截器捕获,就会反复重试导致无限循环;根本解法是用独立的、无响应拦截器的 axios 实例处理刷新请求。
坦白说,这个问题在项目中挺隐蔽的,但它带来的破坏力可不小。当 Token 过期,服务端返回 401 的时候,拦截器会自动跑进去执行刷新操作。可问题来了:如果刷新令牌的请求(比如 /auth/refresh-token 这个接口)本身也是由同一个配置了响应拦截器的 axiosInstance 发起,那它就又会一头撞进同样的错误处理逻辑里。更糟糕的是,这个刷新请求自己也可能返回 401(比如 Refresh Token 过期了),而且因为没打上 _retry 标记,它就会触发新一轮的刷新调用。一环套一环,最终就成了「请求失败 → 触发刷新 → 刷新请求又被拦截 → 再次失败 → 再次触发刷新」的死循环,页面要么卡死,要么重定向彻底失效。核心问题就卡在这里:刷新逻辑被自己设下的“路障”给绊住了。
从代码层面来看,隐患的源头非常集中:
长期稳定更新的攒劲资源: >>>点此立即查看<<<
const response = await axiosInstance.get(REFRESH_TOKEN_ENDPOINT); // 危险!
这里的 axiosInstance 已经绑定了 response.interceptor,那么 REFRESH_TOKEN_ENDPOINT 这个请求一旦失败(比如返回 401),它就会不自觉地再次钻进 interceptors.response.use(...) 的 error 分支,然后又去调 refreshAccessToken()。关键是,originalRequest._retry 对这个新生成的刷新请求根本不起作用——因为它就是个全新的请求,没有 _retry 标记。循环自然而然地就形成了。
答案其实很清晰:必须让刷新 Token 的请求彻底绕过拦截器链。行业里最稳妥的做法是创建一个纯净的、没有任何自定义拦截器的 Axios 实例,专门用来处理认证相关的操作:
// authApi.js —— 纯净实例,仅用于认证接口
import axios from 'axios';
import { REFRESH_TOKEN_ENDPOINT } from '../constants/apiUrlConst';
// 关键:不添加任何 request/response interceptor
export const authApi = axios.create({
baseURL: process.env.REACT_APP_API_URL || '/api',
timeout: 10000,
});
然后在 refreshAccessToken 函数里,就改用这个纯净实例:
import { authApi } from './authApi'; // 引入纯净实例
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../constants/tokenConst';
import { logout } from '../stores/actions/authActions';
import store from '../stores';
const refreshAccessToken = async () => {
const refresh_token = localStorage.getItem(REFRESH_TOKEN_KEY);
if (!refresh_token) {
store.dispatch(logout());
localStorage.clear();
window.location.href = '/';
throw new Error('No refresh token a vailable');
}
try {
// 使用无拦截器的实例,彻底避免循环
const response = await authApi.get(REFRESH_TOKEN_ENDPOINT);
const { access_token, refresh_token: newRefreshToken } = response.data;
localStorage.setItem(ACCESS_TOKEN_KEY, access_token);
localStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken);
return access_token;
} catch (error) {
// 刷新失败:清理状态并强制登出
store.dispatch(logout());
localStorage.clear();
window.location.href = '/login';
throw error; // 确保上层能捕获
}
};
同时,主 axiosInstance 的响应拦截器也需要做些调整,加一道额外的保险:
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const { config, response } = error;
// 仅对非刷新请求且未重试过的情况触发刷新
if (
response.status === 401 &&
!config._retry &&
config.url !== REFRESH_TOKEN_ENDPOINT // 额外保险:显式排除
) {
config._retry = true;
try {
const newToken = await refreshAccessToken();
config.headers.Authorization = `Bearer ${newToken}`;
return axiosInstance(config); // 重发原始请求
} catch (e) {
return Promise.reject(e);
}
}
return Promise.reject(error);
});
把认证流程和业务请求完全解耦,无限循环的问题就能一劳永逸地解决。这样,Token 刷新就会变得健壮、可预测,调试起来也清爽很多。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述