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

Axios请求拦截器刷新token时触发无限循环问题咨询

问题产生的根本原因

两个核心逻辑错误共同触发了无限循环卡死问题:

  • 你在拦截器分支中写的自执行async函数是异步非阻塞的,拦截器不会等待该函数执行完成,就会直接返回config发起请求。你在自执行函数内部修改config.headers的操作完全无效,因为config已经被提前返回用于发起请求了。
  • 你调用刷新token接口使用的是绑定了当前拦截器的同一个axios实例instance,刷新token的请求本身也会被这个请求拦截器拦截。此时旧的过期access_token还没被新token替换,拦截器判断过期后会再次触发刷新token逻辑,无限递归调用刷新接口,最终页面卡死。
  • 额外隐患:没有做刷新请求并发控制,当多个接口同时触发过期判断时,会同时发起多个无效的刷新token请求。
修复方案

按以下规则调整代码即可解决问题:

  • 将请求拦截器的回调函数直接声明为async函数,移除内部无意义的自执行异步IIFE,保证拦截器会等待token刷新完成后再返回请求配置
  • 对刷新token的接口做特殊放行:要么在拦截器中判断到是/refresh接口时直接跳过token校验逻辑,要么单独创建一个未绑定当前鉴权拦截器的axios实例专门用来发刷新token的请求,从根源避免递归触发拦截器
  • 增加刷新状态锁和等待队列,避免token刷新期间重复发起刷新请求,同时保证刷新期间发起的其他接口能在拿到新token后正常携带鉴权头
  • 修正刷新接口的传参格式:axios.post第二个参数为请求体,需和后端要求的参数格式对齐,不要直接传裸token字符串

修复后的核心代码参考:

// 新增刷新锁和等待队列
let isRefreshing = false;
let pendingRequests = [];

instance.interceptors.request.use(
  async (config) => {
    // 刷新接口直接放行,避免递归触发拦截器
    if (config.url === '/refresh') {
      return config;
    }

    const accessToken = localStorage.getItem("access_token");
    // 无token场景直接放行(比如登录接口)
    if (!accessToken) {
      return config;
    }

    const auth = jwt_decode(accessToken);
    const expireTime = auth.exp * 1000;
    const now = Date.now();

    // token未过期,直接携带token放行
    if (expireTime > now) {
      config.headers["Authorization"] = 'Bearer ' + accessToken;
      return config;
    }

    // token过期,处理刷新逻辑
    if (!isRefreshing) {
      isRefreshing = true;
      try {
        const refreshToken = localStorage.getItem("refresh_token");
        // 按后端要求传参,示例为常见的JSON格式传参
        const response = await instance.post("/refresh", { refreshToken });
        const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data;
        
        // 更新本地存储的token
        localStorage.setItem("access_token", newAccessToken);
        localStorage.setItem("refresh_token", newRefreshToken);
        setAuth({ token: newAccessToken, refreshToken: newRefreshToken });

        // 执行等待队列中的请求,替换为新token
        pendingRequests.forEach(cb => cb(newAccessToken));
        pendingRequests = [];

        // 给当前请求携带新token放行
        config.headers["Authorization"] = 'Bearer ' + newAccessToken;
        return config;
      } catch (err) {
        // 刷新失败(比如refresh_token也过期),清空本地存储跳转登录
        localStorage.removeItem("access_token");
        localStorage.removeItem("refresh_token");
        // 此处补充你的跳转登录逻辑,例如 router.push('/login')
        return Promise.reject(err);
      } finally {
        isRefreshing = false;
      }
    } else {
      // 正在刷新token,将当前请求加入等待队列
      return new Promise(resolve => {
        pendingRequests.push((newToken) => {
          config.headers["Authorization"] = 'Bearer ' + newToken;
          resolve(config);
        })
      })
    }
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 调整刷新方法的传参逻辑,和后端格式对齐即可
const refreshAccessToken = async (token) => {
  return instance.post("/refresh", { refreshToken: token });
};

内容的提问来源于stack exchange,提问作者Alpharius

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:39:20