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

使用Axios拦截器刷新JWT Access Token时陷入无限请求循环的解决方法

解决Axios拦截器结合Refresh Token的无限循环问题

核心问题分析

  • 拦截器重复注册:每次调用useAxios都会新增请求拦截器,导致同一请求被多次拦截处理,触发循环。
  • Token值未实时更新:accessToken是Hook初始化时读取的固定值,即便刷新了新Token,判断过期仍用旧值,导致一直判定Token过期。
  • 未更新当前请求的Token:刷新Token后未修改当前请求的Authorization头,请求仍携带旧Token,再次触发过期判断。
  • 无并发请求防重机制:多个请求同时触发Token过期时,会重复调用刷新接口,引发不必要的请求甚至循环。

修正后的代码实现

import axios from 'axios';
import jwtDecode from 'jwt-decode';

// 全局标记是否正在刷新Token,避免并发请求重复触发刷新
let isRefreshing = false;
// 存储等待Token刷新后重试的请求队列
let requestQueue = [];

const useAxios = () => {
  const axiosInstance = axios.create({
    baseURL: process.env.REACT_APP_BASE_URL,
    withCredentials: true
  });

  // 确保拦截器仅注册一次,避免重复堆积
  if (!axiosInstance.interceptors.request.handlers.length) {
    axiosInstance.interceptors.request.use(async (req) => {
      // 实时读取最新的accessToken
      let accessToken = localStorage.getItem("accessToken");
      const refreshToken = getCookie("refreshToken");
      const email = localStorage.getItem("email");

      if (!accessToken) return req;

      // 用最新Token判断是否过期
      const decodedToken = jwtDecode(accessToken);
      const expired = Date.now() >= decodedToken.exp * 1000;

      if (!expired) {
        // 给请求挂载最新的Authorization头
        req.headers.Authorization = accessToken;
        return req;
      }

      // 若正在刷新Token,将当前请求加入队列等待
      if (isRefreshing) {
        return new Promise((resolve) => {
          requestQueue.push(() => {
            req.headers.Authorization = localStorage.getItem("accessToken");
            resolve(req);
          });
        });
      }

      isRefreshing = true;

      try {
        const res = await axios.get(
          `${process.env.REACT_APP_BASE_URL}api/auth/re-issue/${email}`,
          {
            headers: {
              Refresh: refreshToken,
            },
            withCredentials: true
          }
        );

        const newAccessToken = res.headers.get("Authorization");
        localStorage.setItem("accessToken", newAccessToken);

        // 更新当前请求的Token头
        req.headers.Authorization = newAccessToken;

        // 重试队列中所有等待的请求
        requestQueue.forEach(callback => callback());
        requestQueue = [];
      } catch (e) {
        console.error("刷新Token失败:", e);
        // 刷新失败时清除本地Token并跳转登录页,终止循环
        localStorage.removeItem("accessToken");
        window.location.href = "/login";
      } finally {
        isRefreshing = false;
      }

      return req;
    });
  }

  return axiosInstance;
};

export default useAxios;

关键优化点说明

  • 拦截器防重复注册:通过判断axiosInstance.interceptors.request.handlers.length,确保拦截器仅初始化一次。
  • 实时获取Token:在拦截器内部实时读取localStorage中的accessToken,保证过期判断的准确性。
  • 并发请求处理:用isRefreshing标记和requestQueue队列,避免多个请求同时触发Token刷新,刷新完成后统一重试队列请求。
  • 更新当前请求头:刷新Token后立即修改当前请求的Authorization头,避免携带旧Token再次触发过期逻辑。
  • 错误处理优化:刷新Token失败时清除本地Token并跳转登录页,从根源终止循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 02:06:03