基于 React Router 的认证路由守卫与安全重定向机制

275 阅读4分钟

登录重定向组件逻辑详解

在现代前端应用中,用户认证与路由权限控制是核心功能之一。本文详细解析基于 React Router v6+ 的登录重定向机制,涵盖基础保护、安全校验、状态恢复与权限扩展等关键环节。

一、路由守卫组件:ProtectedRoute(基础鉴权)

该路由的核心职责:拦截未认证用户的访问请求,记录原始路径,并安全跳转至登录页。

import { Navigate, useLocation } from "react-router-dom";
import { isAuthenticated } from "@/utils/auth"; // 假设封装了认证逻辑

const ProtectedRoute = ({ children }) => {
  const location = useLocation();

  if (!isAuthenticated()) {
    return (
      <Navigate
        to={{
          pathname: "/login",
          search: `?redirect=${encodeURIComponent(location.pathname + location.search)}`,
        }}
        state={{ from: location }} // 保留完整路由状态 statereplace
      />
    );
  }

  return children;
};

关键点说明

  • encodeURIComponent:防止路径或查询参数包含特殊字符导致解析错误。
  • state: { from }:为后续状态恢复提供上下文。
  • replace: true:避免用户登录后点击“返回”重新进入登录页。

二、登录组件:LoginPage(安全重定向处理)

核心流程

  1. 解析 redirect 参数
  2. 执行登录
  3. 安全验证目标路径
  4. 跳转并恢复状态

优化实现

import { useLocation, useNavigate } from "react-router-dom";
import { loginService } from "@/services/auth";

const LoginPage = () => {
  const navigate = useNavigate();
  const location = useLocation();

  // 解析 redirect 查询参数
  const redirectParam = new URLSearchParams(location.search).get("redirect");
  const from = location.state?.from; // 来源路由状态

  // 安全验证重定向路径
  const getRedirectTarget = () => {
    if (!redirectParam) return from?.pathname || '/';

    try {
      const url = new URL(redirectParam, window.location.origin);
      // 同源检查
      if (url.origin !== window.location.origin) return '/';
      // 防止跳转到登录页自身
      if (url.pathname === '/login') return '/';
      return url.pathname + url.search;
    } catch (e) {
      return '/'; // 解析失败则跳首页
    }
  };

  const handleSubmit = async (credentials) => {
    try {
      await loginService(credentials);

      const target = getRedirectTarget();
      navigate(target, {
        replace: true,
        state: from?.state, // 恢复原始页面状态
      });
    } catch (error) {
      console.error("登录失败:", error);
      // 显示错误提示
    }
  };

  return <LoginForm onSubmit={handleSubmit} />;
};

安全性强化

  • 同源校验:防止开放重定向攻击(Open Redirect)
  • 路径合法性校验:避免跳转到 /login 自身造成循环
  • 异常兜底:非法路径统一跳转至首页

三、增强型路由守卫:AuthGuard(支持角色权限)

适用场景:需要基于角色(RBAC)或权限粒度控制访问的页面。

升级功能:

  1. 支持角色权限验证
  2. 保存完整路由状态
  3. 支持自定义重定向逻辑

实现代码

import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth'; // 自定义 Hook

const AuthGuard = ({
  children,
  roles = [],           // 允许访问的角色列表
  permissions = [],     // 扩展:权限码列表
  customRedirect,       // 自定义失败跳转路径
  onAuthFail,           // 回调钩子
}) => {
  const { user, isAuthenticated } = useAuth();
  const navigate = useNavigate();
  const location = useLocation();

  const hasRole = roles.length === 0 || roles.some(r => user?.roles?.includes(r));
  const hasPermission = permissions.length === 0 || 
    permissions.every(p => user?.permissions?.includes(p));

  // 已经登录且有权访问 
  const canAccess = isAuthenticated && hasRole && hasPermission;

  useEffect(() => {
    if (!canAccess) {
      onAuthFail?.();

      navigate(customRedirect || '/login', {
        replace: true,
        state: {
          from: {
            pathname: location.pathname,
            search: location.search,
            state: location.state,
          },
          authFailedAt: Date.now(),
        },
      });
    }
  }, [isAuthenticated, hasRole, hasPermission, navigate]);

  if (!canAccess) {
    return <div>加载中或无权限...</div>; // 可替换为 Loading 或 403 页面
  }

  return children;
};

🧩 使用示例

<Route element={<AuthGuard roles={['admin']} />}>
  <Route path="/admin" element={<AdminPanel />} />
</Route>

四、状态恢复组件:RouteStateRestorer

功能目标

在页面跳转后恢复滚动位置、模态框、表单状态等。

const RouteStateRestorer = ({ children }) => {
  const location = useLocation();

  useEffect(() => {
    // 恢复滚动位置
    if (location.state?.scrollPosition) {
      window.scrollTo(...location.state.scrollPosition);
    }

    // 恢复模态框
    if (location.state?.modal) {
      openModal(location.state.modal);
    }

    // 恢复表单草稿
    if (location.state?.formDraft) {
      restoreForm(location.state.formDraft);
    }
  }, [location.key]); // location.key 变化时触发(即路由前进/后退)

  return children;
};

💡 提示:可结合 sessionStorage 在刷新后仍能恢复状态。

五、逻辑流程图(文字版)

[用户访问 /dashboard]
         ↓
   ProtectedRoute 渲染
         ↓
   检查 isAuthenticated()
         ↓ No
   记录当前路径 → /login?redirect=%2Fdashboard
         ↓
      跳转至登录页
         ↓
   用户输入账号密码
         ↓
   登录成功 → 校验 redirect 参数安全性
         ↓
   navigate(redirectTarget, { state: from.state })
         ↓
   返回 /dashboard,恢复滚动/模态框等状态

六、关键设计考量(总结与扩展)

维度实践建议
🔒 安全性- 重定向必须同源校验
- 避免 XSS 注入(如 javascript:
- 使用 replace 防止历史污染
📦 状态管理- 利用 location.state 传递上下文
- 可持久化到 sessionStorage 防刷新丢失
⚙️ 性能优化- React.memo 包裹守卫组件
- useCallback 处理事件函数
- 避免在 render 中执行副作用
🛑 错误处理- 捕获无效 redirect 参数
- 设置默认跳转路径(如 /
- 添加重定向循环检测(如记录 authFailedAt 时间戳)
🔁 可扩展性- 支持多级权限(角色 + 权限码)
- 提供 onAuthFail 钩子用于埋点或通知
- 支持自定义登录路径

七、常见问题与解决方案

问题解决方案
刷新后 redirect 丢失?使用 state.from 替代 query 参数,或持久化到 sessionStorage
登录后白屏?检查 navigate 是否正确执行,确认路由是否匹配
无限重定向?添加 authFailedAt 时间戳,防止连续多次跳转
模态框无法恢复?确保 location.state 正确传递,使用 location.key 监听变化

八、推荐项目结构

src/
├── components/
│   ├── auth/
│   │   ├── ProtectedRoute.jsx
│   │   ├── AuthGuard.jsx
│   │   └── RouteStateRestorer.jsx
├── pages/
│   ├── Login.jsx
│   └── Dashboard.jsx
├── hooks/
│   └── useAuth.js
├── utils/
│   └── auth.js
└── services/
    └── auth.js

✅ 总结

该登录重定向体系具备以下优势:

  • 安全可靠:防止开放重定向,支持同源校验
  • 体验流畅:自动跳转 + 状态恢复
  • 灵活扩展:支持角色、权限、自定义逻辑
  • 易于维护:组件化设计,职责分离

📌 建议在实际项目中结合 持久化状态管理(如 Redux、Zustand)埋点监控,进一步提升稳定性和可观测性。