创见博客
React如何实现受保护路由(Protected Router)
七崽爱吃小饼干2026/01/13阅读 0专栏 React

React 受保护路由(Protected Route)

一、受保护路由的核心概念

受保护路由(也叫鉴权路由),是 React 路由开发中高频必备功能,核心作用:对指定的路由页面做访问权限校验,只有满足指定条件(最常见的是「用户已登录」)的情况下,才允许进入该路由页面;如果不满足条件,则强制跳转到指定页面(通常是登录页),拒绝访问。

常见使用场景:

  1. 后台管理系统的首页、用户列表、数据报表等页面,必须登录后才能访问;
  2. 个人中心、订单页面、购物车等页面,需要用户登录鉴权;
  3. 有权限分级的场景,比如「管理员路由」只有管理员账号能访问,普通用户访问会被拦截。

二、前置前提

  1. 本次所有代码都适配 HashRouter / BrowserRouter,二者完全通用,无区别;
  2. 所有示例基于 React Router v6 版本(目前开发唯一主流版本),v6 对受保护路由的写法做了精简优化;
  3. 路由鉴权的核心判断依据:前端存储的用户登录状态,最常用的是 localStorage(持久化,刷新页面不丢失),也可以用 sessionStorage 或 React 状态管理工具(redux/zustand)。

三、受保护路由的实现核心思路(固定逻辑,通用所有场景)

受保护路由本质是 封装一个「鉴权高阶组件」(ProtectedRoute),这个组件是对原生 <Route> 的一层封装,核心执行逻辑只有3步,固定不变:

  1. 接收两个核心参数:需要鉴权的目标组件 element、需要匹配的路由地址 path;
  2. 内部做权限判断:校验用户是否满足访问条件(比如:是否存在登录token、是否是管理员);
  3. 路由分发逻辑:
    • 满足条件:正常渲染「目标组件」,允许访问该路由;
    • 不满足条件:调用 navigate 强制跳转到指定页面(如 /login 登录页),并阻止原路由的渲染。

四、核心API依赖

实现受保护路由,只需要用到 React Router v6 的2个核心钩子,都是你已经掌握的内容,无新API:

  1. useNavigate:用于权限校验失败时,编程式路由跳转(跳登录页);
  2. useLocation:可选,优化体验,记录用户「原本要访问的受保护路由地址」,登录成功后可以回跳到该地址(非常实用的细节优化)。

五、完整实现步骤

步骤1:封装通用的「受保护路由组件」(一次封装,全局复用)

新建文件:src/components/ProtectedRoute.jsx,这是通用组件,项目中所有需要鉴权的路由,都用这个组件替代原生 <Route> 即可,封装一次,终身使用。

jsx
// 通用受保护路由组件 - 核心封装
import { Navigate, useLocation } from 'react-router-dom';

// 接收props:path(路由地址)、element(需要鉴权的组件)
const ProtectedRoute = ({ element }) => {
  // 1. 获取当前要访问的路由地址(用于登录后回跳)
  const location = useLocation();

  // 2. 核心:用户登录状态校验(项目中统一写这里,修改一次即可)
  // 从localStorage获取登录标识,存在则代表已登录,不存在则未登录
  const token = localStorage.getItem('token');
  // 扩展:也可以加其他权限校验,比如 const isAdmin = localStorage.getItem('role') === 'admin'
  const isAuth = !!token; // 转为布尔值,有token=true,无token=false

  // 3. 鉴权核心逻辑
  if (isAuth) {
    // 已登录,正常渲染需要鉴权的组件
    return element;
  } else {
    // 未登录,强制跳转到登录页,并携带原本要访问的地址
    // state: { from: location } 把原地址存入路由状态中
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
};

export default ProtectedRoute;

关键细节说明:

  • <Navigate> 是 React Router v6 的内置组件,作用是「路由重定向」,等价于编程式的 navigate() 跳转;
  • replace 属性:表示替换当前路由历史记录,避免用户跳转登录页后,点击浏览器回退按钮又回到原路由,优化体验;
  • state={{ from: location }}:把用户原本要访问的路由地址存入路由的 state 中,登录成功后可以读取这个地址实现「回跳」。

步骤2:准备基础页面组件(示例用)

准备4个基础页面,满足演示需求,真实项目替换为自己的页面即可:

jsx
// src/pages/Login.jsx 登录页
const Login = () => {
  const handleLogin = () => {
    // 模拟登录逻辑:登录成功后,往localStorage存入token(登录标识)
    localStorage.setItem('token', 'user-login-token-123456');
    // 登录成功后跳转到首页
    window.location.href = '/';
  };

  return (
    <div>
      <h2>登录页面</h2>
      <p>请登录后才能访问后台页面</p>
      <button onClick={handleLogin}>点击模拟登录</button>
    </div>
  );
};
export default Login;

// src/pages/Admin.jsx 受保护的后台首页(必须登录才能访问)
const Admin = () => {
  return <div>后台管理首页 - 已登录用户可见</div>;
};
export default Admin;

// src/pages/Home.jsx 公开首页(无需登录,所有人可访问)
const Home = () => {
  return <div>公开首页 - 无需登录即可访问</div>;
};
export default Home;

// src/pages/NotFound.jsx 404页面
const NotFound = () => {
  return <div>404 - 页面不存在</div>;
};
export default NotFound;

步骤3:在路由配置中使用受保护路由

在 App.jsx 中配置路由,规则明确:

  • 公开路由(无需鉴权):继续使用原生 <Route> 组件;
  • 受保护路由(需要登录/权限):使用我们封装的 <ProtectedRoute> 组件,嵌套在 <Route> 内部。

这种写法是 React Router v6 官方推荐的规范写法,兼容性最强,无任何副作用。

jsx
// src/App.jsx 路由总配置
import { Routes, Route, Link } from 'react-router-dom';
import ProtectedRoute from './components/ProtectedRoute';
import Home from './pages/Home';
import Login from './pages/Login';
import Admin from './pages/Admin';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div className="App">
      {/* 路由导航 */}
      <div style={{ margin: '20px' }}>
        <Link to="/" style={{ marginRight: '20px' }}>公开首页</Link>
        <Link to="/admin" style={{ marginRight: '20px' }}>后台管理(需登录)</Link>
        <Link to="/login">登录页</Link>
      </div>

      {/* 核心路由配置 */}
      <Routes>
        {/* ✅ 公开路由:无需鉴权,直接用Route */}
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />

        {/* ✅ 受保护路由:必须登录才能访问,用封装的ProtectedRoute */}
        <Route 
          path="/admin" 
          element={<ProtectedRoute element={<Admin />} />} 
        />

        {/* 404兜底路由 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

步骤4:优化登录页 - 实现「登录成功后回跳」(实用优化,必加)

这是非常重要的体验优化:用户访问 /admin 时,因为未登录被跳转到 /login,登录成功后,自动跳回用户原本要访问的 /admin 页面,而不是固定跳首页。

修改 src/pages/Login.jsx,新增回跳逻辑,用到 useLocation 和 useNavigate 钩子:

jsx
// 优化后的登录页 - 带回跳功能
import { useLocation, useNavigate } from 'react-router-dom';

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

  // 获取路由状态中存储的「原访问地址」,没有则默认跳首页
  const fromPath = location.state?.from?.pathname || '/';

  const handleLogin = () => {
    // 1. 模拟登录,存入登录标识
    localStorage.setItem('token', 'user-login-token-123456');
    // 2. 登录成功,跳回原本要访问的地址
    navigate(fromPath, { replace: true });
  };

  // 退出登录方法(可选,测试用)
  const handleLogout = () => {
    localStorage.removeItem('token');
    navigate('/login');
  };

  return (
    <div>
      <h2>登录页面</h2>
      <p>请登录后才能访问后台页面</p>
      <button onClick={handleLogin}>点击模拟登录</button>
      <button onClick={handleLogout} style={{ marginLeft: '10px' }}>退出登录</button>
    </div>
  );
};
export default Login;

六、扩展:多角色权限路由

上面的示例是「登录/未登录」的基础鉴权,实际开发中,很多项目有角色权限分级(比如:普通用户、管理员、超级管理员),不同角色能访问的路由不同。

这种需求只需要修改受保护路由组件的「权限判断逻辑」 即可,组件的封装结构完全不变,非常灵活。

示例:只有「管理员」能访问 /admin 路由,普通用户访问被拦截

修改 src/components/ProtectedRoute.jsx:

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

const ProtectedRoute = ({ element, requiredRole = 'user' }) => {
  const location = useLocation();
  // 1. 获取登录标识和用户角色
  const token = localStorage.getItem('token');
  const userRole = localStorage.getItem('role'); // 存储的角色:admin / user
  const isAuth = !!token;
  // 2. 多角色权限判断:已登录 + 角色匹配
  const hasPermission = isAuth && userRole === requiredRole;

  if (hasPermission) {
    return element;
  } else if (isAuth) {
    // 已登录,但角色不匹配,跳转到无权限页面
    return <Navigate to="/no-permission" replace />;
  } else {
    // 未登录,跳登录页
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
};

export default ProtectedRoute;

路由配置中指定需要的角色

jsx
// 只有角色为 admin 的用户才能访问
<Route 
  path="/admin" 
  element={<ProtectedRoute element={<Admin />} requiredRole="admin" />} 
/>

登录时存入角色即可:

jsx
// 管理员登录
localStorage.setItem('token', 'admin-token');
localStorage.setItem('role', 'admin');

// 普通用户登录
localStorage.setItem('token', 'user-token');
localStorage.setItem('role', 'user');

七、受保护路由的嵌套路由适配

如果你的项目用到了嵌套路由(比如 /admin/user、/admin/order),受保护路由依然适用,且写法无变化,只需要把父路由配置为受保护路由即可,子路由会自动继承父路由的鉴权规则。

示例:

jsx
import { Outlet } from 'react-router-dom';
// 父组件 Admin.jsx
const Admin = () => {
  return (
    <div>
      <h2>后台管理首页</h2>
      <div>
        <Link to="/admin/user">用户管理</Link>
        <Link to="/admin/order" style={{ marginLeft: '10px' }}>订单管理</Link>
      </div>
      {/* 子路由出口 */}
      <Outlet />
    </div>
  );
};

// 子组件 User.jsx / Order.jsx
const User = () => <div>用户管理页面 - 仅管理员可见</div>;
const Order = () => <div>订单管理页面 - 仅管理员可见</div>;

路由配置:

jsx
<Routes>
  <Route 
    path="/admin" 
    element={<ProtectedRoute element={<Admin />} requiredRole="admin" />}
  >
    {/* 子路由自动继承鉴权,无需再次封装 */}
    <Route path="user" element={<User />} />
    <Route path="order" element={<Order />} />
  </Route>
</Routes>

特点:父路由 /admin 是受保护的,那么所有子路由都会被自动保护,无需给每个子路由单独配置鉴权,简洁高效。

八、核心注意事项

  1. 受保护路由的权限校验仅在前端生效,是为了提升用户体验,不能替代后端的接口鉴权。后端所有敏感接口,依然需要校验请求头中的 token,防止用户通过手动修改前端存储的token绕过权限。
  2. 登录状态建议存在 localStorage,持久化存储,刷新页面不会丢失;如果是临时登录(关闭页面即退出),可以存在 sessionStorage。
  3. React Router v6 中,不要再用自定义路由表+循环渲染的方式实现受保护路由,官方推荐的 <Route> 嵌套 <ProtectedRoute> 是最稳定的写法,无兼容性问题。
  4. 退出登录时,一定要清除本地存储的登录标识(localStorage.removeItem('token')),否则用户退出后依然能访问受保护路由。

九、总结

  1. React 受保护路由的本质是封装鉴权高阶组件,对原生 <Route> 做一层权限校验的封装;
  2. 核心逻辑固定:校验状态 → 有权限则渲染组件,无权限则重定向;
  3. 基础鉴权(登录/未登录)和多角色鉴权,只是校验逻辑不同,组件封装结构完全一致;
  4. 受保护路由完美兼容嵌套路由,父路由鉴权后,子路由自动继承权限规则;
  5. 所有写法基于 React Router v6 官方规范,可直接在项目中复用,无冗余代码。
评论
0/100