解决Axios调用Refresh Token API后出现的无限循环问题
解决Axios刷新Token后无限循环的永久方案
核心问题分析
你用_retry标记能临时规避循环,但根源问题集中在这几点:
- 刷新Token的请求可能复用了带拦截器的实例,导致刷新失败时再次触发401拦截逻辑
- 未处理刷新Token本身失败的场景(比如Refresh Token过期)
- 并发请求触发401时,会重复调用刷新接口,引发逻辑混乱
具体改进方案
1. 确保刷新Token请求不触发拦截器
刷新接口必须用不带Token拦截器的实例(你已定义mainBackendInstanceWithoutToken),避免刷新失败时再次进入401拦截循环。修改refreshAccessToken的实现,让它使用这个无拦截器实例:
// 调整refreshAccessToken的实现,改用无拦截器实例 export const refreshAccessToken = async () => { const refreshToken = localStorage.getItem('refresh_token'); const response = await mainBackendInstanceWithoutToken.post('/refresh-token', { refresh_token: refreshToken }); // 更新本地存储的Token localStorage.setItem('access_token', response.data.access_token); localStorage.setItem('refresh_token', response.data.refresh_token); return response.data; };
2. 添加全局刷新锁,避免并发请求重复刷新
当多个请求同时触发401时,只执行一次刷新操作,其他请求等待刷新完成后用新Token重试。在拦截器外部定义全局状态:
// 全局变量:存储正在进行的刷新请求Promise let refreshTokenPromise = null;
然后修改响应错误拦截器:
const mainBackendResponseErrorInterceptor = async (error) => { const originalRequest = error.config; // 非401错误直接抛出 if (error.response?.status !== 401) { return Promise.reject(error); } // 已有刷新请求在进行,直接等待完成后重试 if (refreshTokenPromise) { try { await refreshTokenPromise; originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('access_token')}`; return mainBackendInstance(originalRequest); } catch (e) { localStorage.clear(); window.location.reload(); return Promise.reject(e); } } // 无Refresh Token直接跳转登录 const refreshToken = localStorage.getItem('refresh_token'); if (!refreshToken) { localStorage.clear(); window.location.reload(); return Promise.reject(error); } // 发起刷新请求并标记全局状态 refreshTokenPromise = refreshAccessToken().finally(() => { // 刷新完成后重置状态 refreshTokenPromise = null; }); try { await refreshTokenPromise; originalRequest.headers.Authorization = `Bearer ${localStorage.getItem('access_token')}`; return mainBackendInstance(originalRequest); } catch (e) { // 刷新失败(如Refresh Token过期),直接跳转登录 localStorage.clear(); window.location.reload(); return Promise.reject(e); } };
3. 优化请求拦截器的头设置逻辑
原代码直接替换config.headers会丢失原请求的自定义头,改为合并逻辑:
const mainBackendRequestInterceptor = (config) => { const token = getToken(); const companyId = localStorage.getItem('company_id'); // 合并原有headers,保留自定义配置 config.headers = { Accept: 'application/json', 'App-Version-Code': 87, 'Client-Platform': 'web', ...(token && { Authorization: token }), ...(companyId && { 'Company-Id': +companyId }), ...config.headers }; return config; };
4. 确认Axios Retry与拦截器无冲突
你的axiosRetry只针对500错误,和401拦截逻辑不重叠,无需额外修改,但要确保retryCondition仅触发500重试:
axiosRetry(mainBackendInstance, { retries: 2, retryCondition: (error) => error.response?.status === 500 });
最终效果
- 并发请求触发401时,仅执行一次刷新Token操作
- 刷新请求不会触发拦截器,彻底避免嵌套循环
- 刷新失败时直接跳转登录,不会无限重试
- 请求头不会被意外覆盖,保留自定义配置
内容的提问来源于stack exchange,提问作者Sayash Chaudhary
相关产品推荐
相关产品推荐

