【问题标题】:How to restrict access to routes in react-router?如何限制对 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 的回答是我现在使用的,效果很好

标签: javascript reactjs react-router


【解决方案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())。

【讨论】:

【解决方案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。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-15
        • 2018-03-20
        • 2021-01-09
        • 2016-05-12
        • 1970-01-01
        • 2019-07-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多