如何实现 React Router 的路由鉴权

4 min read

React Router (opens in a new tab)React 应用中最常用的路由库之一,路由鉴权是确保应用安全性和数据保护的关键部分。 本文将介绍如何使用 React Router 实现路由鉴权,以及如何限制用户访问特定页面,确保只有经过鉴权的用户可以访问(如登录后、购买会员后等条件)。

以官方文档中的 Auth Example (opens in a new tab) 代码为例, 在线演示可以见 StackBlitz (opens in a new tab)

1. 定义路由

这里我们定义三个路由,分别为 首页 /,登录页 /login,需要登录后才能访问的页面 /protected

export default function App() {
  return (
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<PublicPage />} />
          <Route path="/login" element={<LoginPage />} />
          <Route
            path="/protected"
            element={<ProtectedPage />}
          />
        </Route>
      </Routes>
  );
}
 
function LoginPage() {
  let navigate = useNavigate();
  let location = useLocation();
 
  let from = location.state?.from?.pathname || "/";
 
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
 
    let formData = new FormData(event.currentTarget);
    let username = formData.get("username") as string;
 
    signin(username, () => {
      navigate(from, { replace: true });
    });
  }
 
  return (
    <div>
      <p>You must log in to view the page at {from}</p>
 
      <form onSubmit={handleSubmit}>
        <label>
          Username: <input name="username" type="text" />
        </label>{" "}
        <button type="submit">Login</button>
      </form>
    </div>
  );
}
 
function PublicPage() {
  return <h3>Public</h3>;
}
 
function ProtectedPage() {
  return <h3>Protected</h3>;
}

2. 实现路由拦截组件

实现路由拦截组件只需要判断用户是否登录即可,也可以按照项目实际需求判断条件。这个组件的作用是用来包裹需要鉴权的路由组件,如果不符合判断条件则跳转到登录页面。

function RequireAuth({ children }: { children: JSX.Element }) {
  let auth = useAuth();
  let location = useLocation();
  
  // 这里判断用户是否登录,如果未登录则跳转到登录页面
  if (!auth.user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
 
  return children;
}

判断用户登录官方示例代码中是使用 Context 这个 React 特性实现的 AuthProvider ,便于不同组件获取共用的状态。 我们也可以使用其他符合项目的方式判断用户是否登录,比如将用户登录状态存到全局状态库(如 Redux)或者 localStorage 中,示例代码如下:

const isLogin = useAppSelector((state) => state.user.isLogin);
const userInfo = useAppSelector((state) => state.user.userInfo);
const dispatch = useAppDispatch();
 
useEffect(() => {
  if (isLogin && !userInfo) {
    dispatch(fetchUserInfo());
  }
}, [dispatch, isLogin, userInfo]);
 
if (!isLogin) {
  return <Navigate to="/login" state={{ from: location }} replace />;
}

3. 为需要鉴权的路由增加路由拦截组件包裹

最后只需要为需要鉴权的路由增加路由拦截组件即可,这么实现充分的利用的 React 的组合式思想,可以方便的为是否需要鉴权的路由增减鉴权功能(便于配置维护), 也通过封装统一的组件提高了代码的复用,逻辑改变时只需要修改通用组件即可。

export default function App() {
  return (
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<PublicPage />} />
          <Route path="/login" element={<LoginPage />} />
          <Route
            path="/protected"
            element={
              <RequireAuth>
                <ProtectedPage />
              </RequireAuth>
            }
          />
        </Route>
      </Routes>
  );
}

参考链接

2023 © OXXD.RSS