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

React中Axios拦截器未等待失败请求的问题排查

说明

受保护路由中的请求需在验证完成后才可处理,Access Token和Refresh Token存储在Cookie中并自动附加至所有必要请求。

问题

当Access Token过期后,后端返回400状态码,原本应该在Axios拦截器中执行刷新Token逻辑,但实际流程是先执行了受保护路由的验证请求,之后才执行刷新操作,导致页面直接跳转到首页,没有利用刷新后的Token重新验证。

截图

请求顺序截图

Axios拦截器代码

import axios from 'axios';
import { API } from '@/constants/api';
import AuthService from '@/services/AuthService';

export const axiosInstance = axios.create({
  baseURL: API.base,
  timeout: 15000,
  withCredentials: true,
});

axiosInstance.interceptors.request.use(
  async (config) => config,
  async (error) => Promise.reject(error),
);

axiosInstance.interceptors.response.use(
  async (response) => response,
  async (error) => {
    const config = error?.config;

    if (error?.response?.status === 400 && !config?.sent) {
      await AuthService.refreshTokenPair();

      return axiosInstance.request(config);
    }
    return Promise.reject(error);
  },
);
export default axiosInstance;

受保护路由代码

const ProtectedRoute = observer((): JSX.Element => {
  const { isAuth } = userStore;
  const handleUnauthorized = () => {
    userStore.isAuth = false;
  };

  useLayoutEffect(() => {
    const verify = async () => {
      try {
        const verifyDetails = await AuthService.verifyAccessToken();
        const details = await UserService.getUserDetails(verifyDetails?.id);
        if (!details) {
          handleUnauthorized();
          return;
        }
        userStore.user = details;
        userStore.isAuth = true;
      } catch (err) {
        handleUnauthorized();
      }
    };

    verify();
  }, []);

  if (!isAuth) {
    return <Navigate to={ROUTES.home.path} replace />;
  }

  return <Outlet />;
});
export default memo(ProtectedRoute);

问题分析与解决方案

核心问题

  1. Axios拦截器未标记config.sent,可能导致同一请求重复触发刷新逻辑;且刷新Token后重试请求时,受保护路由的验证流程已经因初始请求失败执行了跳转逻辑。
  2. 受保护路由的verify函数在请求失败后直接执行handleUnauthorized,没有考虑拦截器刷新Token后重试的可能性。

修复步骤

  1. 完善Axios拦截器的刷新标记
    在进入刷新逻辑时给请求配置添加sent标记,避免重复刷新:

    axiosInstance.interceptors.response.use(
      async (response) => response,
      async (error) => {
        const config = error?.config;
    
        if (error?.response?.status === 400 && !config?.sent) {
          config.sent = true; // 添加标记,防止重复触发刷新
          await AuthService.refreshTokenPair();
          return axiosInstance.request(config);
        }
        return Promise.reject(error);
      },
    );
    
  2. 修改ProtectedRoute的验证逻辑
    在verify函数捕获错误后,判断是否可通过刷新Token重试验证,而非直接跳转:

    useLayoutEffect(() => {
      const verify = async () => {
        try {
          const verifyDetails = await AuthService.verifyAccessToken();
          const details = await UserService.getUserDetails(verifyDetails?.id);
          if (!details) {
            handleUnauthorized();
            return;
          }
          userStore.user = details;
          userStore.isAuth = true;
        } catch (err) {
          // 尝试刷新Token后重新验证
          try {
            await AuthService.refreshTokenPair();
            // 刷新成功后重新执行验证流程
            const verifyDetails = await AuthService.verifyAccessToken();
            const details = await UserService.getUserDetails(verifyDetails?.id);
            if (details) {
              userStore.user = details;
              userStore.isAuth = true;
              return;
            }
          } catch (refreshErr) {
            // 刷新Token失败再执行跳转
            handleUnauthorized();
          }
          handleUnauthorized();
        }
      };
    
      verify();
    }, []);
    
  3. 优化状态联动(可选)
    在状态管理(如userStore)中添加tokenRefreshed状态,刷新Token后更新该状态,让useLayoutEffect依赖此状态自动重新触发验证:

    // 拦截器中刷新Token后更新状态
    await AuthService.refreshTokenPair();
    userStore.tokenRefreshed = !userStore.tokenRefreshed;
    
    // ProtectedRoute的useLayoutEffect添加依赖
    useLayoutEffect(() => {
      // ... 验证逻辑
    }, [userStore.tokenRefreshed]);
    

调整后,当Access Token过期时,拦截器会先完成Token刷新,再重试验证请求,受保护路由会根据重试结果判断是否跳转,避免提前触发首页跳转逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:22:55