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

How to restrict access to routes in react-router?

有谁知道如何在 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>

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

这是一个例子

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 对象的 React mixins。这个对象不应该在这两个 mixins 之外可用。这是一个例子:

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())。

更新(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。我们在应用程序首次加载时执行此操作。另外值得一提的是,您可能希望在进行身份验证时在您的应用程序上添加一个加载标志 运行,这样您就不会在每次刷新页面时都刷新登录页面。

然后我们将标志传递给我们的路线。我们创建两种类型的路由 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)。

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

这是一个 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

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} />
];

Redirect

的 React Router 4 文档中有(现在?)这方面的示例
import { Route, Redirect } from 'react-router'

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

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/.

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

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

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

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。