人人都会AI编程

12.4 路由守卫与权限控制实现

更新时间:2026-07-10

路由守卫是前端权限控制的核心环节,用于在用户访问某个路由之前进行拦截校验,决定允许通过、重定向还是降级展示。在 React Router v6 中,没有内置的“守卫”钩子,但我们可以通过封装组件 + 编程式判断来实现灵活的权限控制体系。

12.4.1 典型场景

  • 认证守卫:未登录用户无法访问特定页面,自动跳转到登录页。
  • 角色/权限守卫:已登录但权限不足时,展示 403 无权限页面或重定向到首页。
  • 路由菜单过滤:根据权限动态生成侧边栏菜单,隐藏无权访问的入口。

12.4.2 实现基础:封装受保护路由组件

通常会创建一个 ProtectedRoute 组件,接收权限校验条件,根据结果渲染目标组件或重定向。

import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ isAllowed, redirectPath = '/login', children }) {
  const location = useLocation();

  if (!isAllowed) {
    // 将用户试图访问的路径通过 state 传递,登录后可跳转回来
    return <Navigate to={redirectPath} replace state={{ from: location }} />;
  }

  return children;
}

isAllowed 可以是一个布尔值或一个函数,由外层根据认证状态和权限规则计算而来。

认证守卫示例

function AuthRoute({ children }) {
  const { user } = useAuth(); // 自定义 hook,从 Context 或状态管理中获取用户信息
  const isLoggedIn = !!user;

  return (
    <ProtectedRoute isAllowed={isLoggedIn} redirectPath="/login">
      {children}
    </ProtectedRoute>
  );
}

在路由配置中使用:

<Routes>
  <Route path="/login" element={<LoginPage />} />
  <Route path="/dashboard" element={
    <AuthRoute>
      <Dashboard />
    </AuthRoute>
  } />
</Routes>

12.4.3 权限控制:基于角色或权限点

简单的登录守卫只能区分“已登录/未登录”,实际项目往往需要更细粒度的权限(如管理员、普通用户、编辑者等)。将权限信息(角色列表、权限标识)存储在用户状态中,然后在路由上附加权限要求。

定义权限标识

const ROLES = {
  ADMIN: 'admin',
  EDITOR: 'editor',
  USER: 'user',
};

// 路由配置扩展 meta 信息
const routes = [
  {
    path: '/admin',
    element: <AdminPanel />,
    requiredRoles: [ROLES.ADMIN],
  },
  {
    path: '/editor',
    element: <EditorPanel />,
    requiredRoles: [ROLES.EDITOR, ROLES.ADMIN],
  },
  {
    path: '/profile',
    element: <Profile />,
    requiredRoles: [], // 任何已登录用户
  },
];

权限校验组件

function RoleBasedRoute({ children, requiredRoles }) {
  const { user } = useAuth();
  const userRoles = user?.roles || [];

  const hasRequiredRole = requiredRoles.length === 0
    ? true
    : requiredRoles.some(role => userRoles.includes(role));

  return (
    <ProtectedRoute isAllowed={!!user && hasRequiredRole} redirectPath="/403">
      {children}
    </ProtectedRoute>
  );
}

然后渲染路由时动态包裹:

<Routes>
  {routes.map(route => (
    <Route
      key={route.path}
      path={route.path}
      element={
        <RoleBasedRoute requiredRoles={route.requiredRoles || []}>
          {route.element}
        </RoleBasedRoute>
      }
    />
  ))}
</Routes>

如果用户权限不足,重定向到 /403 页面告知“无权限”,比直接跳转登录更友好。

12.4.4 动态生成侧边栏菜单

权限控制不只在路由访问时生效,也应在 UI 上体现——用户看不到自己无权访问的菜单项。

function SidebarMenu() {
  const { user } = useAuth();
  const userRoles = user?.roles || [];

  const menuItems = [
    { label: '仪表盘', path: '/dashboard', roles: [] },           // 所有人可见
    { label: '管理面板', path: '/admin', roles: [ROLES.ADMIN] },
    { label: '编辑面板', path: '/editor', roles: [ROLES.EDITOR, ROLES.ADMIN] },
  ];

  const filteredItems = menuItems.filter(item =>
    item.roles.length === 0 || item.roles.some(role => userRoles.includes(role))
  );

  return (
    <nav>
      {filteredItems.map(item => (
        <Link key={item.path} to={item.path}>{item.label}</Link>
      ))}
    </nav>
  );
}

12.4.5 集中式权限配置与高阶组件

更工程化的做法是在路由配置中集中声明权限,然后通过一个统一的工厂函数或高阶组件生成最终的路由元素。

function withPermission(Component, requiredRoles) {
  return function PermissionComponent() {
    const { user } = useAuth();
    const userRoles = user?.roles || [];

    const hasPermission = requiredRoles.length === 0
      || requiredRoles.some(r => userRoles.includes(r));

    if (!user) return <Navigate to="/login" replace />;
    if (!hasPermission) return <Navigate to="/403" replace />;
    return <Component />;
  };
}

// 使用
const AdminPage = withPermission(AdminPanel, [ROLES.ADMIN]);

这种方式让路由配置更简洁,权限逻辑与路由声明解耦。

12.4.6 小技巧与注意事项

  • 登录后回跳:在跳转登录页时,通过 state 保存来源路径,登录成功后使用 useNavigate 跳转回 state.from 或默认页。
  • 前端权限仅作体验优化:真正的安全防线在后端,所有敏感接口必须进行权限校验,前端路由守卫无法防范直接 URL 访问或 API 调用。
  • 异步权限获取:如果权限信息是从接口获取的,在应用初始化时先展示 loading,待权限就绪再渲染路由,否则会出现闪烁或被误拦截。
  • 嵌套路由:对于嵌套路由,守卫可以放在父级 <Route>element 中,这样所有子路由都会受到保护。
  • 多种权限模型:除了角色,还可以使用策略式权限点(如 canEditPost),将其作为 requiredPermissions 数组匹配。

通过灵活组合 ProtectedRoute、角色/权限校验、动态菜单过滤,就可以搭建起一套健壮且用户友好的前端权限控制体系。这套方案可随项目规模平滑演进。