使用反应路由器 v6 的受保护路由
Posted
技术标签:
【中文标题】使用反应路由器 v6 的受保护路由【英文标题】:Protected route with react router v6 【发布时间】:2020-10-04 15:20:22 【问题描述】:用新版本 6 的 react-router 编写 ProtectedRoute 的正确方法是什么? 我写了这个,但这不是路线
const PrivateRoute = ( component: Component, ...props ) =>
if (!Component) return null;
return props.isAuthenticated
? <Component />
: <Navigate to=props.redirectLink />
export default PrivateRoute;
【问题讨论】:
嗯,没有你说的这不是一条路线。我相信你的做法是正确的。 React-Router 中没有直接的东西。您需要编写这样的包装器。但是,我不确定您的代码中的Navigate
是什么。
"如果您更喜欢使用声明式 API 进行导航(ala v5 的 Redirect 组件),v6 提供了 Navigate 组件。"你可以在这里找到更多信息github.com/ReactTraining/react-router/blob/dev/docs/…
【参考方案1】:
这是我使用 useRoutes 实现私有路由的工作示例。
App.js
import routes from './routes';
import useRoutes from 'react-router-dom';
function App()
const isLoggedIn = useSelector((state) => state.auth);
const routing = useRoutes(routes(isLoggedIn));
return (
<>
routing
</>
);
routes.js
import Navigate,Outlet from 'react-router-dom';
const routes = (isLoggedIn) => [
path: '/app',
element: isLoggedIn ? <DashboardLayout /> : <Navigate to="/login" />,
children: [
path: '/dashboard', element: <Dashboard /> ,
path: '/account', element: <Account /> ,
path: '/', element: <Navigate to="/app/dashboard" /> ,
path: 'member',
element: <Outlet />,
children: [
path: '/', element: <MemberGrid /> ,
path: '/add', element: <AddMember /> ,
],
,
],
,
path: '/',
element: !isLoggedIn ? <MainLayout /> : <Navigate to="/app/dashboard" />,
children: [
path: 'login', element: <Login /> ,
path: '/', element: <Navigate to="/login" /> ,
],
,
];
export default routes;
【讨论】:
这是我认为在这里唯一有意义的... @fmsthird ...确定你是否已经在使用 redux。 我得到了这个错误!错误:useRoutes() 只能在useRoutes()
实现。谢谢!
这对我来说很有意义,谢谢【参考方案2】:
这个例子来自react-router-dom
:https://github.com/remix-run/react-router/blob/main/examples/auth/README.md
然后修改成这个https://stackblitz.com/edit/github-5kknft?file=src%2FApp.tsx
export default function App()
return (
<AuthProvider>
<Routes>
<Route element=<Layout />>
<Route path="/" element=<PublicPage /> />
<Route path="/public" element=<PublicPage /> />
<Route path="/login" element=<LoginPage /> />
<Route element=<RequireAuth />>
<Route path="/protected" element=<ProtectedPage /> />
<Route path="/dashboard" element=<Dashboard /> />
</Route>
</Route>
<Route path="*" element=<NotFound /> />
</Routes>
</AuthProvider>
);
function RequireAuth()
let auth = useAuth();
let location = useLocation();
if (!auth.user)
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state= from: location />;
return <Outlet />;
【讨论】:
这比用RequireAuth
包装每个element
道具的“官方”方式要好得多——使用这种方法有什么问题吗?或者它是否按预期工作?跨度>
【参考方案3】:
这是来自React Router documentation 的官方指南。
与其为
<Route>
元素创建包装器以获得所需的功能,不如在<Route element>
属性中完成所有自己的组合。以上面的例子为例,如果你想在 React Router v6 中保护某些路由免受未经身份验证的用户的攻击,你可以这样做:
import Routes, Route, Navigate from "react-router-dom";
function App()
return (
<Routes>
<Route path="/public" element=<PublicPage /> />
<Route
path="/protected"
element=
// Good! Do your composition here instead of wrapping <Route>.
// This is really just inverting the wrapping, but it's a lot
// more clear which components expect which props.
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
/>
</Routes>
);
function RequireAuth( children, redirectTo )
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to=redirectTo />;
请注意,在此示例中,
RequireAuth
组件不期望任何<Route>
的道具。这是因为它不想表现得像<Route>
。相反,它只是在<Route>
中呈现。
【讨论】:
【参考方案4】:所有不错的选择。您还可以根据身份验证状态(或任何其他状态)简单地呈现不同的路由处理。您不必使用原始 javascript 对象方法。
不要忘记,您可以使用立即调用的匿名内部函数 (() => COMPONENT)()
来动态决定哪个组件处理特定的 <Route/>
。
这些示例可能尚未包含在 v6
的初步文档中,因为处理私有 <Route/>
s 实际上非常简单。
例如
<Routes>
state.authed ?
// Wait until we have the current user...
currentUser ?
<Route
path='/'
element=(() =>
// Show a "no access" message if the user is NOT an App Admin doesn't have access to any schools at all (which includes not having access to anything INSIDE any school either)
if (!currentUser.appAdministrator && currentUser.schoolIds?.length === 0) return <AdminNoAccess />
return <Outlet />
)()
>
<Route
path='/'
element=(() =>
// If the user is a super user, we return the <SuperAdmin /> component, which renders some of its own routes/nav.
if (currentUser.appAdministrator) return <SuperAdmin />
return <Outlet />
)()
>
<Route
path='schools'
element=(() =>
if (currentUser.schoolIds?.length === 1)
return <Navigate to=`schools/schoolId` />
else
return <AdminSchools />
)()
/>
<Route path='users' children=<Users /> />
</Route>
<Route path=`schools/:schoolId` element=<AdminSchool /> />
<Route path='*' element=<Navigate to='schools' /> />
</Route>
:
null
:
<>
<Route path='login' element=<Login /> />
<Route path='signup' element=<Signup /> />
<Route path='forgot-password' element=<ForgotPassword /> />
<Route path='reset-password' element=<ResetPassword /> />
<Route path='*' element=<Navigate to='login' /> />
</>
</Routes>
【讨论】:
【参考方案5】:您需要编写一个小型包装器并使用Navigate
组件进行重定向。您还需要渲染路线
const Container = (Component, redirectLink, isAuthenticated, ...props) =>
if(!isAuthenticated)
return <Navigate to=redirectLink />;
return <Component ...props />
const PrivateRoute = ( component: Component, redirectLink, isAuthenticated, path, ...props ) =>
return (
<Route
path=path
element=<Container redirectLink=redirectLink isAuthenticate=isAuthenticated Component=Component />
/>
)
export default PrivateRoute;
您可以在the github docs
上找到迁移指南
【讨论】:
warning.js:24 你应该在 useEffect 中调用 navigate(),而不是在组件第一次渲染时调用。 更新解决方案【参考方案6】:这是一个工作示例。
import React from 'react';
import Route, Navigate from 'react-router-dom';
const PrivateRoute = ( component: Component, redirectTo, isAuth, path, ...props ) =>
if(!isAuth)
return <Navigate to=redirectTo />;
return <Route path=path element=<Component /> />
;
export default PrivateRoute;
用法:
<Routes>
<Route path="app" element=<DashboardLayout />>
<PrivateRoute isAuth=true path="account" component=AccountView redirectTo='/login'/>
</Route>
</Routes>
【讨论】:
这不再适用于反应路由器 v6.0.2【参考方案7】:这是我最新的 react-router v6 beta 工作实现。我不知道如何使用 useRoutes 实现受保护的路由。他们的文档应该添加一个关于如何以两种方式实现受保护/私有路由的示例。
ProtectedRoute 组件
import React from 'react';
import PropTypes from 'prop-types';
import Route from 'react-router-dom';
import Forbidden from '../../views/errors/Forbidden';
import useAuth from '../../contexts/AuthContext';
const ProtectedRoute = ( roles, element, children, ...rest ) =>
const user, login = useAuth();
if (!user)
login();
return <></>;
if (roles.length > 0)
const routeRoles = roles.map((role) => role.toLowerCase());
const userRoles = (user && user.roles ? user.roles : []).map((role) => role.toLowerCase());
if (miscUtils.intersection(routeRoles, userRoles).length === 0)
return <Forbidden />;
return (
<Route element=element ...rest>
children
</Route>
);
;
ProtectedRoute.propTypes =
roles: PropTypes.arrayOf(PropTypes.string),
element: PropTypes.element,
children: PropTypes.node,
;
ProtectedRoute.defaultProps =
roles: [],
element: null,
children: null,
;
export default ProtectedRoute;
AppRoutes 组件
import React from 'react';
import Routes, Route, Navigate, Outlet from 'react-router-dom';
import Login from './components/oauth/Login';
import Logout from './components/oauth/Logout';
import RenewToken from './components/oauth/RenewToken';
import ProtectedRoute from './components/ProtectedRoute';
import NotFound from './views/errors/NotFound';
import Index from './views/Index';
import MainContainer from './views/MainContainer';
import ViewUserProfile from './views/user/profile/ViewUserProfile';
import CreateUserProfile from './views/user/profile/CreateUserProfile';
import UpdateUserProfile from './views/user/profile/UpdateUserProfile';
import PartnerProfile from './views/partner/profile/PartnerProfile';
const AppRoutes = () =>
return (
<Routes>
/* auth pages (important: do not place under /auth path) */
<Route path="oauth/login" element=<Login /> />
<Route path="oauth/logout" element=<Logout /> />
<Route path="oauth/renew" element=<RenewToken /> />
<Route element=<MainContainer />>
<Route path="/" element=<Index /> />
/* protected routes */
<ProtectedRoute path="user" element=<Outlet />>
<Route path="/" element=<Navigate to="profile" replace /> />
<Route path="profile" element=<Outlet />>
<Route path="/" element=<ViewUserProfile /> />
<Route path="create" element=<CreateUserProfile /> />
<Route path="update" element=<UpdateUserProfile /> />
</Route>
</ProtectedRoute>
<ProtectedRoute path="partner" roles=['partner'] element=<Outlet />>
<Route path="/" element=<Navigate to="profile" replace /> />
<Route path="profile" element=<PartnerProfile /> />
</ProtectedRoute>
</Route>
<Route path="*" element=<NotFound /> />
</Routes>
);
;
export default AppRoutes;
【讨论】:
这是检查用户角色的更好解决方案。我希望我可以投票两次:D【参考方案8】:这是一个对 TypeScript 更友好的实现,它重用了 react-router v6 中的 RouteProps
:
import React from 'react';
import RouteProps from 'react-router';
import Route, Navigate from 'react-router-dom';
import useAuthState from '../../contexts';
export interface PrivateRouteProps extends RouteProps
redirectPath: string;
export const PrivateRoute = ( redirectPath, ...props : PrivateRouteProps) =>
const user = useAuthState();
if (!user)
return <Navigate to=redirectPath />;
return <Route ...props />;
;
useAuthState
是一个钩子,可以在用户登录时检索用户。
我就是这样使用它的:
<Routes>
<Route path="/" element=<Home /> />
<PrivateRoute path="/admin" redirectPath="/signin" element=<Admin /> />
<Route path="*" element=<NotFound /> />
</Routes>
【讨论】:
你使用 Firebase 吗?【参考方案9】:我不知道是否有人回答了这个问题,但我是这样做的。
import Navigate, Route from "react-router-dom";
<Route path="*" element=<Navigate to="/register" /> />
【讨论】:
保护在哪里?【参考方案10】:这是 BrowserRouter 作为路由器的结构:
const AppRouter = () =>
return (
<Router>
<Layout>
<Routes>
<Route exact path="" element=<Home /> />
<Route exact path="login" element=<Login /> />
<Route exact path="register" element=<Register /> />
// These are the Private Components
<Route
exact
path="/account"
element=
<PrivateRoute>
<Account />
</PrivateRoute>
/>
<Route
exact
path="/quizzes"
element=
<PrivateRoute>
<Quizzes />
</PrivateRoute>
/>
<Route
exact
path="/quizz/:quizzid"
element=
<PrivateRoute>
<Quizz />
</PrivateRoute>
/>
<Route
exact
path="/admin/users"
element=
<PrivateRoute>
<Users />
</PrivateRoute>
/>
<Route exact path="*" element=<NotFound /> />
</Routes>
</Layout>
</Router>
);
;
这是 PrivateRoute:
import Navigate from "react-router-dom";
import useAuth from "../auth/useAuth";
function PrivateRoute( children )
const auth = useAuth();
return auth.user ? children : <Navigate to="/login" />;
export default PrivateRoute;
【讨论】:
【参考方案11】:您可以使用auth-react-router
包https://www.npmjs.com/package/auth-react-router
它提供了一个非常简单的 API 来定义您的路由和更多配置(例如授权和未授权路由的重定向路由,每个路由的后备组件)
用法:
-
定义路线
// routes.tsx
import React from 'react';
import IRoutesConfig from 'auth-react-router';
import LoginPage from '../pages/LoginPage.tsx';
// public lazy loaded pages
const LazyPublicPage = React.lazy(() => import('../pages/PublicPage.tsx'));
// private lazy loaded pages
const LazyPrivatePage = React.lazy(() => import('../pages/PrivatePage.tsx'));
const LazyProfilePage = React.lazy(() => import('../pages/ProfilePage.tsx'));
export const routes: IRoutesConfig =
publicRedirectRoute: '/profile', // redirect to `/profile` when authorized is trying to access public routes
privateRedirectRoute: '/login', // redirect to `/login` when unauthorized user access a private route
defaultFallback: <MyCustomSpinner />,
public: [
path: '/public',
component: <LazyPublicPage />,
,
path: '/login',
component: <LoginPage />,
,
],
private: [
path: '/private',
component: <LazyPrivatePage />,
,
path: '/profile',
component: <LazyProfilePage />
,
],
common: [
path: '/',
component: <p>common</p>,
,
path: '*',
component: <p>page not found 404</p>,
,
],
;
-
将它们链接到您的应用程序
import AppRouter, Routes from 'auth-react-router';
import BrowserRouter from 'react-router-dom';
import routes from './routes';
export const App = () =>
const isAuth = useAuthProvider();
return (
<BrowserRouter>
<AppRouter isAuth=isAuth routes=routes>
/* Wrap `Routes` component into a Layout component or add Header */
<Routes />
</AppRouter>
</BrowserRouter>
);
;
【讨论】:
以上是关于使用反应路由器 v6 的受保护路由的主要内容,如果未能解决你的问题,请参考以下文章