17370845950

React 中实现双向路由保护:登录用户禁访注册/登录页,未登录用户禁访仪表盘

本文介绍如何在 react router v6 中通过自定义路由守卫(如 `privateroute` 和 `anonymousroute`)统一管理双向访问控制——既限制未登录用户访问受保护页面,也阻止已登录用户重复进入登录、注册等无需认证的页面。

在构建现代 React 应用时,仅实现“登录后才能访问 Dashboard”是不够的;同样重要的是防止已登录用户误入 /login、/register 或 /forgotpassword 等无意义甚至存在安全风险的页面。理想方案应保持代码简洁、职责清晰,且不需为每个组件手动添加条件判断。

✅ 推荐做法:分离关注点,创建专用守卫组件

最佳实践是将认证逻辑拆分为两个语义明确的守卫组件:

  • PrivateRoute:仅允许已登录用户渲染子路由(典型权限控制)
  • AnonymousRoute:仅允许未登录用户渲染子路由(反向保护)

二者共享基础鉴权逻辑,但行为互斥,职责分明:

// src/components/routing/PrivateRoute.jsx
import { Outlet, Navigate } from 'react-router-dom';

export function PrivateRoute() {
  const user = JSON.parse(localStorage.getItem('user'));
  return user ?  : ;
}

// src/components/routing/AnonymousRoute.jsx
import { Outlet, Navigate } from 'react-router-dom';

export function AnonymousRoute() {
  const user = JSON.parse(localStorage.getItem('user'));
  return user ?  : ;
}

使用方式简洁直观,符合 React Router v6 的嵌套路由设计哲学:


  
  
    {/* 受保护的路由组:仅登录用户可访问 */}
    }>
      } />
      } />
    

    {/* 匿名路由组:仅未登录用户可访问 */}
    
      } />
      } />
      } />
    

    {/* 可选:404 页面(放在最后) */}
    } />
  
⚠️ 注意事项:localStorage.getItem('user') 仅作示意,生产环境建议使用更健壮的认证状态管理(如结合 JWT 解析、useAuth 自定义 Hook 或 Context + Redux);始终使用 replace: true 避免登录后点击浏览器「返回」按钮重新跳转至登录页;Outlet 是 v6 的关键占位符,必须用于包裹嵌套子路由,不可省略。

? 替代方案:单组件双模式(不推荐,仅作兼容参考)

若因历史原因必须复用单一组件,可扩展为带配置参数的通用守卫 ProtectedRoute:

function ProtectedRoute({ isAuth = true, target = '/login' }) {
  const user = JSON.parse(localStorage.getItem('user'));
  if (isAuth) {
    return user ?  : ;
  }
  return user ?  : ;
}

调用时显式声明意图:

}>
  } />

}>
  } />

但该方式牺牲了可读性与可维护性,违背单一职责原则,强烈建议优先采用 PrivateRoute + AnonymousRoute 分离方案

✅ 总结

  • ✅ 使用两个专用守卫组件,语义清晰、易于测试与复用;
  • ✅ 利用 和嵌套路由结构,避免重复包装每个页面组件;
  • ✅ 所有重定向均启用 replace,保障导航栈干净;
  • ❌ 避免在组件内部做 useEffect 跳转或 window.location 硬跳转,破坏 React Router 的声明式路由一致性。

通过这种结构化设计,你不仅能精准控制用户流向,还能为未来扩展角色权限(如 AdminRoute)、多因子验证流程等打下坚实基础。