You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:35:21