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

空依赖列表下useEffect仍引发无限循环?路由守卫组件故障排查

RequireAuth路由守卫触发无限循环问题排查与解决

问题现象

使用RequireAuth组件作为路由守卫时,访问受保护路由会触发无限循环,控制台被大量getting token日志刷屏,同时出现路由跳转限流、token验证403等错误。

路由守卫代码

import { Navigate, Outlet, useLocation } from "react-router-dom";
import useAuth from "../hooks/useAuth";
import axios from "../api/axios";
import { useEffect, useState } from "react";

const RequireAuth = ({allowedRole}) => {
    const { getRole, getUser, getToken } = useAuth();
    const location = useLocation();

    const [isAuthenticated, setIsAuthenticated] = useState(false);

    useEffect(() => {
        
        const token = getToken(); 
        const verifyToken = async () => {

            try {
                const response = await axios.get("/api/verifyToken", {
                    headers: {
                        Authorization: `Bearer ${token}`,
                    },
                });

                setIsAuthenticated(response.data.valid);
            } catch (error) {
                console.error("Error verifying token:", error);
            }
        };

        verifyToken();
    }, []);


    return isAuthenticated ? (
    allowedRole?.includes(getRole()) ? (
      <Outlet />
    ) : getUser() ? (<Navigate to="/unauthorized" state={{ from: location }} replace />) 
                  : (<Navigate to="/login" state={{ from: location }} replace />)
    ) : (
    <Navigate to="/login" state={{ from: location }} replace />
    );
}

export default RequireAuth;

getToken方法实现

const getToken = () => {
        console.log('getting token');
        return auth ? auth?.token : null;
    };

控制台报错信息

  • 重复日志:
    AuthProvider.js:49 getting token
    AuthProvider.js:49 getting token
    ...(重复输出)
    
  • Token验证错误:
    GET http://localhost:3500/api/verifyToken 403 (Forbidden)
    Failed to load resource: the server responded with a status of 403 ()
    Error verifying token: AxiosError
    
  • React更新深度超限警告:
    react-dom.development.js:86 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
    
  • 路由跳转限流提示:
    Throttling navigation to prevent the browser from hanging. See https://crbug.com/1038223. Command line switch --disable-ipc-flooding-protection can be used to bypass the protection
    

问题分析

  1. 无限循环根源:

    • 路由配置错误:若/login、/unauthorized等公开路由被RequireAuth包裹,会导致跳转后再次触发路由守卫,形成循环。
    • 组件重渲染频繁:AuthProvider中的getToken、getRole等方法未用useCallback包裹,每次组件渲染都会生成新的函数实例,导致依赖项不稳定,触发重复渲染。
    • useEffect依赖缺失:useEffect为空依赖数组,但内部调用了随渲染变化的getToken方法,可能导致验证逻辑意外重复执行。
  2. 403错误与后端无日志:

    • 请求格式问题:Authorization头的Bearer与token之间可能缺少空格,或token无效/已过期。
    • 后端中间件拦截:认证中间件可能在请求到达/api/verifyToken路由前就返回了403,未触发后端路由的日志输出。
    • CORS配置问题:前端请求可能被浏览器CORS策略拦截,导致实际未到达后端(或后端CORS未正确配置)。

解决方案

1. 修正路由配置

确保公开路由不被RequireAuth组件包裹,示例如下:

import { Routes, Route } from "react-router-dom";
import RequireAuth from "./components/RequireAuth";
import Login from "./components/Login";
import Unauthorized from "./components/Unauthorized";
import Dashboard from "./components/Dashboard";

function App() {
  return (
    <Routes>
      {/* 公开路由 */}
      <Route path="/login" element={<Login />} />
      <Route path="/unauthorized" element={<Unauthorized />} />

      {/* 受保护路由 */}
      <Route element={<RequireAuth allowedRole={["admin"]} />}>
        <Route path="/dashboard" element={<Dashboard />} />
      </Route>
    </Routes>
  );
}

export default App;

2. 优化AuthProvider中的方法

使用useCallback包裹getToken、getRole、getUser,避免每次渲染生成新函数:

import { useCallback, useState, createContext, useContext } from "react";

const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState(null);

  const getToken = useCallback(() => {
    console.log('getting token');
    return auth ? auth?.token : null;
  }, [auth]);

  const getRole = useCallback(() => {
    return auth ? auth?.role : null;
  }, [auth]);

  const getUser = useCallback(() => {
    return auth ? auth?.user : null;
  }, [auth]);

  return (
    <AuthContext.Provider value={{ auth, setAuth, getToken, getRole, getUser }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

3. 修正RequireAuth的useEffect逻辑

补充依赖数组,增加空token判断,确保失败时正确设置认证状态:

useEffect(() => {
  const token = getToken();
  // 空token直接标记为未认证
  if (!token) {
    setIsAuthenticated(false);
    return;
  }

  const verifyToken = async () => {
    try {
      const response = await axios.get("/api/verifyToken", {
        headers: {
          Authorization: `Bearer ${token}`, // 确保Bearer后有空格
        },
      });
      setIsAuthenticated(response.data.valid);
    } catch (error) {
      console.error("Error verifying token:", error);
      // 验证失败强制标记为未认证
      setIsAuthenticated(false);
    }
  };

  verifyToken();
}, [getToken]); // 加入getToken作为依赖

4. 排查后端403问题

  • 检查Authorization头格式:确保Bearer与token之间存在空格,例如Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...。
  • 检查后端认证中间件:确认中间件是否会在token无效时直接返回403,且未记录日志;可在中间件中添加日志输出,排查token验证过程。
  • 验证CORS配置:确保后端允许前端请求来源,可通过浏览器网络面板查看请求是否存在CORS相关错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 20:04:53