如何限制对 react-router 中路由的访问?

Posted

技术标签:

【中文标题】如何限制对 react-router 中路由的访问?【英文标题】:How to restrict access to routes in react-router? 【发布时间】:2015-09-14 02:13:01 【问题描述】:

有谁知道如何限制对 react-router 中特定路由的访问?我想在允许访问特定路线之前检查用户是否已登录。我认为这很简单,但文档并不清楚如何做到这一点。

这是我应该在我定义 <Route> 组件的地方进行设置,还是应该在我的组件处理程序中处理它?

<Route handler=App path="/">
  <NotFoundRoute handler=NotFound name="not-found"/>
  <DefaultRoute handler=Login name="login"/>
  <Route handler=Todos name="todos"/> /* I want this to be restricted */
</Route>

【问题讨论】:

如果他们没有登录,重定向到登录处理程序。另请注意,客户端可以访问它加载的所有 JS,因此不要在其中存储敏感信息。 @Tanner Semerad 你有任何关于你是如何实现这一目标的 github 存储库吗? @jit 我没有,抱歉。下面来自 miciek 的答案是我所需要的,但请记住,这是在 react-router 1.0 之前。我知道自 1.0 发布以来,许多事情发生了变化,但大多相似。 @jayair 的回答是我现在使用的,效果很好 【参考方案1】:

通常会授予登录用户一个令牌,并使用此令牌与服务器进行任何通信。我们通常做的是定义一个根页面,并在该页面之上构建。此根页面为您进行本地化、身份验证和其他配置。

这是一个例子

Routes = (
    <Route path="/" handler=Root>
        <Route name="login" handler=Login />
        <Route name="forget" handler=ForgetPassword />
        <Route handler=Main >
            <Route name="overview" handler=Overview />
            <Route name="profile" handler=Profile />
            <DefaultRoute handler=Overview />
        </Route>
        <DefaultRoute handler=Login />
        <NotFoundRoute handler=NotFound />
    </Route>
);

在您的根页面上,检查令牌是否为空或向服务器验证令牌以查看用户是否有效登录。

希望这会有所帮助:)

【讨论】:

对,那么如果 Auth 没有通过,或者“Main”处理程序是什么样的,我将如何停止导入“Overview”类?例如,如果“Overview”具有需要经过身份验证的应用程序才能运行的依赖项怎么办?因为它是为了在路由器上运行而导入的,所以它的所有依赖项也会被导入,因此你的应用程序损坏了,对吧? 这不能回答所提出的问题【参考方案2】:

如果您想在整个应用程序中使用身份验证,则需要在应用程序范围内存储一些数据(例如令牌)。你可以设置两个 React mixin 来负责管理 $auth 对象。这个对象不应该在这两个 mixin 之外可用。这是一个例子:

define('userManagement', function() 
    'use strict';

    var $auth = 
        isLoggedIn: function () 
            // return something, e.g. using server-stored data
        
    ;

    return 
        Authenticator: 
           login: function(username, password) 
               // modify $auth object, or call server, or both
           
        ,

        NeedsAuthenticatedUser: 
            statics: 
                willTransitionTo: function (transition) 
                    if (!$auth.isLoggedIn()) 
                        transition.abort();
                    
                
            
        
    ;
);

然后您可以将Authenticator 混合到您的登录组件(登录屏幕、登录弹出窗口等)中,并在您拥有所有必要数据时调用this.login 函数。

最重要的是通过混入NeedsAuthenticatedUser mixin 来保护您的组件。每个需要经过身份验证的用户的组件都必须如下所示:

var um = require('userManagement');

var ProtectedComponent = React.createClass(
    mixins: [um.NeedsAuthenticatedUser]
    // ...

请注意,NeedsAuthenticatedUser 使用 react-router API(willTransitionTotransition.abort())。

【讨论】:

Mixins 是个坏主意。 Read more 我发现了一个更好的方法:github.com/reactjs/react-router/tree/master/examples/auth-flow Mixins 已从 ES6 中移除,React 已弃用它们。【参考方案3】:

更新(2019 年 8 月 16 日)

在 react-router v4 和使用 React Hooks 中,这看起来有点不同。让我们从你的App.js开始吧。

export default function App() 
  const [isAuthenticated, userHasAuthenticated] = useState(false);

  useEffect(() => 
    onLoad();
  , []);

  async function onLoad() 
    try 
      await Auth.currentSession();
      userHasAuthenticated(true);
     catch (e) 
      alert(e);
    
  

  return (
    <div className="App container">
      <h1>Welcome to my app</h1>
      <Switch>
        <UnauthenticatedRoute
          path="/login"
          component=Login
          appProps= isAuthenticated 
        />
        <AuthenticatedRoute
          path="/todos"
          component=Todos
          appProps= isAuthenticated 
        />
        <Route component=NotFound />
      </Switch>
    </div>
  );

我们正在使用Auth 库来检查用户当前是否已通过身份验证。将此替换为您的身份验证检查功能。如果是这样,那么我们将isAuthenticated 标志设置为true。我们在 App 首次加载时执行此操作。另外值得一提的是,您可能希望在运行身份验证检查时在您的应用上添加一个加载标志,这样您就不会在每次刷新页面时都闪烁登录页面。

然后我们将标志传递给我们的路线。我们创建了两种类型的路由AuthenticatedRouteUnauthenticatedRoute

AuthenticatedRoute.js 看起来像这样。

export default function AuthenticatedRoute( component: C, appProps, ...rest ) 
  return (
    <Route
      ...rest
      render=props =>
        appProps.isAuthenticated
          ? <C ...props ...appProps />
          : <Redirect
              to=`/login?redirect=$props.location.pathname$props.location.search`
            />
    />
  );

它检查isAuthenticated 是否设置为true。如果是,那么它将呈现所需的组件。如果没有,那么它会重定向到登录页面。

另一方面,UnauthenticatedRoute.js 看起来像这样。

export default ( component: C, appProps, ...rest ) =>
  <Route
    ...rest
    render=props =>
      !appProps.isAuthenticated
        ? <C ...props ...appProps />
        : <Redirect to="/" />
  />;

在这种情况下,如果 isAuthenticated 设置为 false,它将呈现所需的组件。如果设置为 true,它会将您发送到主页。

您可以在我们的指南中找到详细版本 - https://serverless-stack.com/chapters/create-a-route-that-redirects.html

旧版本

接受的答案是正确的,但 React 团队认为 Mixins 是有害的 (https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html)。

如果有人遇到这个问题并正在寻找推荐的方法来做到这一点,我建议使用高阶组件而不是 Mixins。

这是一个 HOC 示例,它将在继续之前检查用户是否已登录。如果用户未登录,它会将您重定向到登录页面。该组件采用名为isLoggedIn 的道具,基本上是您的应用程序可以存储的一个标志,用于表示用户是否已登录。

import React from 'react';
import  withRouter  from 'react-router';

export default function requireAuth(Component) 

  class AuthenticatedComponent extends React.Component 

    componentWillMount() 
      this.checkAuth();
    

    checkAuth() 
      if ( ! this.props.isLoggedIn) 
        const location = this.props.location;
        const redirect = location.pathname + location.search;

        this.props.router.push(`/login?redirect=$redirect`);
      
    

    render() 
      return this.props.isLoggedIn
        ? <Component  ...this.props  />
        : null;
    

  

  return withRouter(AuthenticatedComponent);

要使用这个 HOC,只需将它包裹在您的路线周围。在您的示例中,它将是:

<Route handler=requireAuth(Todos) name="todos"/>

我在这里的详细分步教程中介绍了这个和其他一些主题 - https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

【讨论】:

如果我的原始代码使用 ,我将如何使它与这个示例一起工作? 我有非常相似的代码,但我的问题是,是否足够安全?我的意思是可能是攻击者可以更改 JS 缩小代码,以便将 this.props.isLoggedIn 替换为 true 并绕过登录? @karimelhelawy 确实如此,因此您需要在服务器的 API 中强制执行身份验证。 &lt;Route handler=/&gt; 在 v1.0 中已弃用,您应该使用 &lt;Route component= /&gt; componentWillMount 很快就会被弃用。 Read it in the blog post on reactjs.org。相反,我会选择@jacob 提供的答案。【参考方案4】:

react-router 鼓励为您的路由器使用声明性方法,您应该使您的路由器尽可能地笨拙,并避免将您的路由逻辑放在您的组件中。

您可以这样做(假设您将loggedIn 属性传递给它):

const DumbRouter = ( loggedIn ) => (
  <Router history=history>
    <Switch>
      [
        !loggedIn && LoggedOutRoutes,
        loggedIn && LoggedInRouter,
        <Route component=404Route />
      ]
    </Switch>
  </Router>
);

const LoggedInRoutes = [
  <Route path="/" component=Profile />
];

const LoggedOutRoutes = [
  <Route path="/" component=Login />
];

【讨论】:

这个很简单,很好。问题是,如果您已注销或登录,您通常希望识别相同的路由,因此如果用户已注销,您可以正确重定向到登录。您通常希望路由相同,但根据登录状态以不同的方式运行。此外,使用您的解决方案,您正在添加重复,通过在 2 个更难维护的不同位置创建相同的路线。【参考方案5】:

(现在?)在 React Router 4 的 Redirect 文档中有一个例子

import  Route, Redirect  from 'react-router'

<Route exact path="/" render=() => (
  loggedIn ? (
    <Redirect to="/dashboard"/>
  ) : (
    <PublicHomePage/>
  )
)/>

【讨论】:

如何使用“loggedIn”作为函数或变量?你能解释一下吗 @KunvarSingh 它应该是一个函数,因为值发生了变化。【参考方案6】:

private-route.tsx

import Redirect, Route, RouteProps from 'react-router';
import * as React from 'react';

interface PrivateRouteProps extends RouteProps 
  /**
   * '/login' for example.
   */
  redirectTo: string;

  /**
   * If true, won't redirect.
   * We are using a function instead of a bool, a bool does not seem to be updated
   * after having successfully authenticated.
   */
  isLogged: () => boolean;



export function PrivateRoute(props: PrivateRouteProps) 
  // `component: Component` is not typing, it assign the value to a new variable.
  let  isLogged, redirectTo, component: Component, ...rest : any = props;

  // error: JSX type element Component does not have call signature or ... AVOIDED BY ADDING ANY, still work,
  // and did not find a proper way to fix it.
  return <Route ...rest render=(props) => (
    isLogged()
      ? <Component ...props/>
      : <Redirect to=
        pathname: redirectTo,
        state:  from: props.location 
       />
  ) />;

用法:

        <PrivateRoute exact=true 
                      path="/admin/" 
                      redirectTo='/admin/login' 
                      isLogged=this.loginService.isLogged 
                      component=AdminDashboardPage/>
        <Route path="/admin/login/" component=AdminLoginPage/>

基于https://tylermcginnis.com/react-router-protected-routes-authentication/。

【讨论】:

【参考方案7】:

您可以使用 HOC,而 auth 是一个变量,您可以更改值 true 或 false 表示(授权)

<Route path="/login" component=SignIn />
<Route path="/posts" render = () => (auth ?  (<Post />) : (<Redirect to="/login" />))/>

【讨论】:

【参考方案8】:

您可以避免在确认身份验证之前渲染组件,如下所示:

import  useState, useEffect, useRef  from 'react';
import  useHistory  from 'react-router-dom';

const Route = () => 
    const [loading, sertLoading] = useState(true);
    const history = useHistory();

    const ref = useRef<Function>();

    // must use ref!
    ref.current.routeGuard = () => 
        const authenticationHandler = (): boolean => 
         // do authentication here
        
        sertLoading(true);
        const go = authenticationHandler();
        if (go === false) 
            history.goBack();
        
        sertLoading(false);
     

    useEffect(() => 
        ref.current.routeGuard();
        history.listen(() => 
            ref.current.routeGuard();
        );
    , []);

    return (
        <>
            !loading && <YourRouteComponent />
        </>
    )
;

或者简单地说,yarn add react-routers,哪个组件有 props beforeEachbeforeRoute 像 Vue Route。

【讨论】:

以上是关于如何限制对 react-router 中路由的访问?的主要内容,如果未能解决你的问题,请参考以下文章

阻止用户访问其他路由 - ReactJS - React-Router -

通过在 react-router 的地址栏上输入它的地址直接访问路由器

NodeJS express:限制对路由的访问

如果使用 history.push 访问特定路由,React-Router v5 不会重定向

如何在 react-router v4 的根路由上设置可选参数?

react-router简介