使用反应路由器 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 的官方指南。

与其为 &lt;Route&gt; 元素创建包装器以获得所需的功能,不如在 &lt;Route element&gt; 属性中完成所有自己的组合。

以上面的例子为例,如果你想在 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 组件不期望任何&lt;Route&gt; 的道具。这是因为它不想表现得像&lt;Route&gt;。相反,它只是在 &lt;Route&gt; 中呈现。

【讨论】:

【参考方案4】:

所有不错的选择。您还可以根据身份验证状态(或任何其他状态)简单地呈现不同的路由处理。您不必使用原始 javascript 对象方法。

不要忘记,您可以使用立即调用的匿名内部函数 (() =&gt; COMPONENT)() 来动态决定哪个组件处理特定的 &lt;Route/&gt;

这些示例可能尚未包含在 v6 的初步文档中,因为处理私有 &lt;Route/&gt;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 的受保护路由的主要内容,如果未能解决你的问题,请参考以下文章

如何使用反应路由器 dom v6 在路由更改时滚动到顶部?

使用反应路由器 V6 和刷新页面时我得到空白页面

反应路由器 v6 中的查询字符串

使用 react router dom v6 保护路由 渲染没有返回任何内容

在另一个组件 V6 中反应路由

以编程方式重定向到反应路由器 v6 中的路由的问题