Javascript 如何在 React Router 4 中实现经过身份验证的路由?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43164554/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:43:15  来源:igfitidea点击:

How to implement authenticated routes in React Router 4?

javascriptreactjsreact-routerreact-router-v4

提问by Jiew Meng

I was trying to implement authenticated routes but found that React Router 4 now prevents this from working:

我试图实现经过身份验证的路由,但发现 React Router 4 现在阻止了它的工作:

<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
    <Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
    <Route exact path="/domains" component={DomainsIndex} />
</Route>

The error is:

错误是:

Warning: You should not use <Route component>and <Route children>in the same route; <Route children>will be ignored

警告:您不应在同一路线上使用<Route component><Route children><Route children>将被忽略

In that case, whats the correct way to implement this?

在这种情况下,实现这一点的正确方法是什么?

It appears in react-router(v4) docs, it suggests something like

它出现在react-router(v4) 文档中,它暗示了类似

<Router>
    <div>
    <AuthButton/>
    <ul>
        <li><Link to="/public">Public Page</Link></li>
        <li><Link to="/protected">Protected Page</Link></li>
    </ul>
    <Route path="/public" component={Public}/>
    <Route path="/login" component={Login}/>
    <PrivateRoute path="/protected" component={Protected}/>
    </div>
</Router>

But isit possible to achieve this while grouping a bunch of routes together?

但是有可能在将一堆路由组合在一起时实现这一目标吗?



UPDATE

更新

Ok, after some research, I came up with this:

好吧,经过一番研究,我想出了这个:

import React, {PropTypes} from "react"
import {Route} from "react-router-dom"

export default class AuthenticatedRoute extends React.Component {
  render() {
    if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <Route {...this.props} />
  }
}

AuthenticatedRoute.propTypes = {
  isLoggedIn: PropTypes.bool.isRequired,
  component: PropTypes.element,
  redirectToLogin: PropTypes.func.isRequired
}

Isit correct to dispatch an action in render()it feels wrong. It doesnt really seem correct with componentDidMountor some other hook either?

render()其中发送动作是否正确,感觉是错误的。使用componentDidMount或其他一些钩子似乎也不正确?

回答by Tyler McGinnis

You're going to want to use the Redirectcomponent. There's a few different approaches to this problem. Here's one I like, have a PrivateRoute component that takes in an authedprop and then renders based on that props.

您将要使用该Redirect组件。这个问题有几种不同的方法。这是我喜欢的一个,有一个 PrivateRoute 组件,它接收一个authed道具,然后根据该道具进行渲染。

function PrivateRoute ({component: Component, authed, ...rest}) {
  return (
    <Route
      {...rest}
      render={(props) => authed === true
        ? <Component {...props} />
        : <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
    />
  )
}

Now your Routes can look something like this

现在你的Routes 看起来像这样

<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />

If you're still confused, I wrote this post that may help - Protected routes and authentication with React Router v4

如果您仍然感到困惑,我写了这篇文章可能会有所帮助 - Protected routes and authentication with React Router v4

回答by MrDuDuDu

Tnx Tyler McGinnis for solution. I make my idea from Tyler McGinnis idea.

Tnx Tyler McGinnis 寻求解决方案。我的想法来自 Tyler McGinnis 的想法。

const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
  return (
    <Route
      {...rest}

      render={
        decisionFunc()
          ? trueComponent
          : falseComponent
      }
    />
  )
}

You can implement that like this

你可以像这样实现

<DecisionRoute path="/signin" exact={true}
            trueComponent={redirectStart}
            falseComponent={SignInPage}
            decisionFunc={isAuth}
          />

decisionFunc just a function that return true or false

DecisionFunc 只是一个返回 true 或 false 的函数

const redirectStart = props => <Redirect to="/orders" />

回答by Hemanthvrm

NEW SOLUTION(Using Redux for state management)

新解决方案(使用 Redux 进行状态管理)

If user try to access any url, first i am going to check if access token available, if not redirect to login page, Once user log in using login page, we do store that in localstorage as well as in our redux state. (localstorage or cookies..we keep this topic out of context for now).
since redux state as updated, now we do have access token so we gonna redirect to home page.

如果用户尝试访问任何 url,首先我将检查访问令牌是否可用,如果没有重定向到登录页面,一旦用户使用登录页面登录,我们会将其存储在 localstorage 以及我们的 redux 状态中。(localstorage 或 cookies ..我们暂时将这个主题与上下文无关)。
由于 redux 状态已更新,现在我们确实有访问令牌,因此我们将重定向到主页。

This is kind of basic, if we want RBAC implemented then decode your jwt access token, as payload in access token do have role information. Just redirect them based on role (admin/user/guest).(Will update this post if anyone looking for sample).

这是基本的,如果我们想要 RBAC 实现然后解码您的 jwt 访问令牌,因为访问令牌中的有效负载确实具有角色信息。只需根据角色(管理员/用户/访客)重定向它们。(如果有人寻找示例,将更新此帖子)。

So what if user try to insert dummy token in localstorage. As we do have access token, we will redirect to home component. My home component will make rest call to grab data, since jwt token was dummy, rest call will return unauthorized user. So i do call logout (which will clear localstorage and redirect to login page again)

那么如果用户尝试在 localstorage 中插入虚拟令牌怎么办。由于我们有访问令牌,我们将重定向到 home 组件。我的 home 组件将进行 rest 调用以获取数据,因为 jwt 令牌是虚拟的,rest 调用将返回未经授权的用户。所以我确实调用了注销(这将清除本地存储并再次重定向到登录页面)

index.js

索引.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import history from './utils/history';

import Store from './statemanagement/store/configureStore';
import Privateroutes from './Privateroutes';
import Logout from './components/auth/Logout';

ReactDOM.render(
  <Store>
    <Router history={history}>
      <Switch>
        <Route path="/logout" exact component={Logout} />
        <Route path="/" exact component={Privateroutes} />
        <Route path="/:someParam" component={Privateroutes} />
      </Switch>
    </Router>
  </Store>,
  document.querySelector('#root')
);

History.js

历史.js

import { createBrowserHistory as history } from 'history';

export default history({});

Privateroutes.js

私有路由.js

import React, { Fragment } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import App from './components/App';
import Home from './components/home';
import Login from './components/auth/Login';
import Notfound from './components/404';

const Privateroutes = props => {
  const { accessToken } = props.authData;
  if (accessToken) {
    return (
      <Fragment>
        <App>
          <Switch>
            <Route exact path="/" component={Home} />
            <Route path="/login" render={() => <Redirect to="/" />} />
            <Route exact path="/home" component={Home} />
            <Route path="/404" component={Notfound} />
            <Route path="*" render={() => <Redirect to="/404" />} />
          </Switch>
        </App>
      </Fragment>
    );
  } else {
    return (
      <Fragment>
        <Route exact path="/login" component={Login} />
        <Route exact path="*" render={() => <Redirect to="/login" />} />
      </Fragment>
    );
  }
};

const mapStateToProps = state => {
  const { authData } = state.user;
  return {
    authData: authData
  };
};

export default connect(mapStateToProps)(Privateroutes);

OLD ANSWER

旧答案

Just adding my solution to the problem.

只是将我的解决方案添加到问题中。

I am using jwt tokens for authentication, So if user has that token then i will redirect them to home page or else i will redirect them to signin page on default (which is this route '/'). So once user was logged in and try to access signin page url (in my case '/') . I will redirect them to home by default('/home').

我使用 jwt 令牌进行身份验证,因此如果用户拥有该令牌,那么我会将它们重定向到主页,否则我会将它们重定向到默认登录页面(这是此路由“/”)。因此,一旦用户登录并尝试访问登录页面 url (在我的情况下为 '/') 。默认情况下,我会将它们重定向到 home('/home')。

And my components do have HOC named requireAuth to check if user token is valid.if not then call signout action which removes localhistory token.

我的组件确实有名为 requireAuth 的 HOC 来检查用户令牌是否有效。如果不是,则调用注销操作以删除本地历史令牌。

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect  } from 'react-router-dom';  

//and also import appropriate components

//middleware

  class checkStatus extends React.Component {
        render() {
              if(localStorage.getItem('token')){
                return (
                  <Fragment>
                    <App>
                      <Route path="/home" exact component={Overview} />
                      <Route path="/home/add" exact component={Add} />
                      <Route path="/signout" component={Signout} />
                      <Route path="/details" component={details} />
                      <Route exact path="/" render={() => <Redirect to="/home" />} />
                    </App>

                </Fragment>
                )
              }else{
                return (
                  <Fragment>
                    <Route path="/" exact component={Signin} />
                    <Redirect to="/"  />
                  </Fragment>
                )
              }
         } }

    ReactDOM.render(   <Provider store={store}>
        <BrowserRouter>
          <Switch >
              <Route path="/" exact component={checkStatus} />
              <Route path="/:someParam"  component={checkStatus}/>
          </Switch >
        </BrowserRouter>   </Provider>,   document.querySelector('#root')
);

回答by Felipe Augusto

Based on the answer of @Tyler McGinnis. I made a different approach using ES6 syntaxand nested routeswith wrapped components:

基于@Tyler McGinnis的回答。我使用ES6 语法和带有包装组件的嵌套路由做了一个不同的方法:

import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'

const PrivateRoute = ({ children, authed, ...rest }) =>
  <Route
    {...rest}
    render={(props) => authed ?
      <div>
        {Children.map(children, child => cloneElement(child, { ...child.props }))}
      </div>
      :
      <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
  />

export default PrivateRoute

And using it:

并使用它:

<BrowserRouter>
  <div>
    <PrivateRoute path='/home' authed={auth}>
      <Navigation>
        <Route component={Home} path="/home" />
      </Navigation>
    </PrivateRoute>

    <Route exact path='/' component={PublicHomePage} />
  </div>
</BrowserRouter>

回答by Jose G Varanam

install react-router-dom

安装 react-router-dom

then create two components one for valid users and other for invalid users.

然后创建两个组件,一个用于有效用户,另一个用于无效用户。

try this on app.js

在 app.js 上试试这个

import React from 'react';

import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';

import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;

class App extends React.Component {
 render() {
    return ( 
      <Router>
      <div>
        <Route exact path="/" render={() =>(
          loggedin ? ( <Route  component={ValidUser} />)
          : (<Route component={InValidUser} />)
        )} />

        </div>
      </Router>
    )
  }
}
export default App;

回答by Gonzalo Ca?ada

I know it's been a while but I've been working on an npm packagefor private and public routes.

我知道已经有一段时间了,但我一直在为私有和公共路由开发npm 包

Here's how to make a private route:

以下是创建私有路由的方法:

<PrivateRoute?exact?path="/private"?authed={true}?redirectTo="/login"?component={Title}?text="This?is?a?private?route"/>

And you can also make Public routes that only unauthed user can access

您还可以创建只有未经授权的用户才能访问的公共路由

<PublicRoute?exact?path="/public"?authed={false}?redirectTo="/admin"?component={Title}?text="This?route?is?for?unauthed?users"/>

I hope it helps!

我希望它有帮助!

回答by Varun Kumar

I implemented using-

我使用 -

<Route path='/dashboard' render={() => (
    this.state.user.isLoggedIn ? 
    (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
    (<Redirect to="/login" />)
)} />

authenticate props will be passed to components e.g. signup using which user state can be changed. Complete AppRoutes-

身份验证道具将传递给组件,例如使用可以更改用户状态的注册。完整的 AppRoutes-

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';

import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';

import { config } from '../utils/Config';

export default class AppRoutes extends React.Component {

    constructor(props) {
        super(props);

        // initially assuming that user is logged out
        let user = {
            isLoggedIn: false
        }

        // if user is logged in, his details can be found from local storage
        try {
            let userJsonString = localStorage.getItem(config.localStorageKey);
            if (userJsonString) {
                user = JSON.parse(userJsonString);
            }
        } catch (exception) {
        }

        // updating the state
        this.state = {
            user: user
        };

        this.authenticate = this.authenticate.bind(this);
    }

    // this function is called on login/logout
    authenticate(user) {
        this.setState({
            user: user
        });

        // updating user's details
        localStorage.setItem(config.localStorageKey, JSON.stringify(user));
    }

    render() {
        return (
            <Switch>
                <Route exact path='/' component={Home} />
                <Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
                <Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
                <Route path='/dashboard' render={() => (
                    this.state.user.isLoggedIn ? 
                            (<Dashboard authenticate={this.authenticate} user={this.state.user} />) : 
                            (<Redirect to="/login" />)
                )} />
            </Switch>
        );
    }
} 

Check the complete project here: https://github.com/varunon9/hello-react

在这里查看完整的项目:https: //github.com/varunon9/hello-react

回答by Todd Chaffee

It seems your hesitation is in creating your own component and then dispatching in the render method? Well you can avoid both by just using the rendermethod of the <Route>component. No need to create a <AuthenticatedRoute>component unless you really want to. It can be as simple as below. Note the {...routeProps}spread making sure you continue to send the properties of the <Route>component down to the child component (<MyComponent>in this case).

似乎您在创建自己的组件然后在 render 方法中调度时犹豫不决?那么你可以通过使用组件的render方法来避免两者<Route><AuthenticatedRoute>除非您真的想要,否则无需创建组件。它可以像下面一样简单。注意{...routeProps}传播确保您继续将<Route>组件的属性向下发送到子组件(<MyComponent>在这种情况下)。

<Route path='/someprivatepath' render={routeProps => {

   if (!this.props.isLoggedIn) {
      this.props.redirectToLogin()
      return null
    }
    return <MyComponent {...routeProps} anotherProp={somevalue} />

} />

See the React Router V4 render documentation

请参阅React Router V4 渲染文档

If you did want to create a dedicated component, then it looks like you are on the right track. Since React Router V4 is purely declarative routing(it says so right in the description) I do not think you will get away with putting your redirect code outside of the normal component lifecycle. Looking at the code for React Router itself, they perform the redirect in either componentWillMountor componentDidMountdepending on whether or not it is server side rendering. Here is the code below, which is pretty simple and might help you feel more comfortable with where to put your redirect logic.

如果您确实想创建一个专用组件,那么您似乎走在正确的轨道上。由于 React Router V4纯粹是声明性路由(它在描述中这么说),我认为您不会将重定向代码置于正常组件生命周期之外。查看 React Router 本身代码,它们执行重定向componentWillMountcomponentDidMount取决于它是否是服务器端渲染。这是下面的代码,它非常简单,可能会帮助您更舒适地放置重定向逻辑的位置。

import React, { PropTypes } from 'react'

/**
 * The public API for updating the location programatically
 * with a component.
 */
class Redirect extends React.Component {
  static propTypes = {
    push: PropTypes.bool,
    from: PropTypes.string,
    to: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.object
    ])
  }

  static defaultProps = {
    push: false
  }

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.shape({
        push: PropTypes.func.isRequired,
        replace: PropTypes.func.isRequired
      }).isRequired,
      staticContext: PropTypes.object
    }).isRequired
  }

  isStatic() {
    return this.context.router && this.context.router.staticContext
  }

  componentWillMount() {
    if (this.isStatic())
      this.perform()
  }

  componentDidMount() {
    if (!this.isStatic())
      this.perform()
  }

  perform() {
    const { history } = this.context.router
    const { push, to } = this.props

    if (push) {
      history.push(to)
    } else {
      history.replace(to)
    }
  }

  render() {
    return null
  }
}

export default Redirect

回答by Varun Kumar

My Previous answer is not scalable. Here is what I think is good approach-

我以前的答案不可扩展。这是我认为的好方法-

Your Routes-

您的路线-

<Switch>
  <Route
    exact path="/"
    component={matchStateToProps(InitialAppState, {
      routeOpen: true // no auth is needed to access this route
    })} />
  <Route
    exact path="/profile"
    component={matchStateToProps(Profile, {
      routeOpen: false // can set it false or just omit this key
    })} />
  <Route
    exact path="/login"
    component={matchStateToProps(Login, {
      routeOpen: true
    })} />
  <Route
    exact path="/forgot-password"
    component={matchStateToProps(ForgotPassword, {
      routeOpen: true
    })} />
  <Route
    exact path="/dashboard"
    component={matchStateToProps(DashBoard)} />
</Switch>

Idea is to use a wrapper in componentprops which would return original component if no auth is required or already authenticated otherwise would return default component e.g. Login.

想法是在component道具中使用包装器,如果不需要身份验证或已经过身份验证,它将返回原始组件,否则将返回默认组件,例如登录。

const matchStateToProps = function(Component, defaultProps) {
  return (props) => {
    let authRequired = true;

    if (defaultProps && defaultProps.routeOpen) {
      authRequired = false;
    }

    if (authRequired) {
      // check if loginState key exists in localStorage (Your auth logic goes here)
      if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
        return <Component { ...defaultProps } />; // authenticated, good to go
      } else {
        return <InitialAppState { ...defaultProps } />; // not authenticated
      }
    }
    return <Component { ...defaultProps } />; // no auth is required
  };
};

回答by Anupam Maurya

Here is the simple clean protected route

这是简单干净的保护路线

const ProtectedRoute 
  = ({ isAllowed, ...props }) => 
     isAllowed 
     ? <Route {...props}/> 
     : <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=> 
    <Switch>
      <Route exact path="/authentificate" component={Login}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/secrets" 
         component={Secrets}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/polices" 
         component={Polices}/>
      <ProtectedRoute 
         isAllowed={isTokenVerified} 
         exact 
         path="/grants" component={Grants}/>
      <Redirect from="/" to={lastTab}/>
    </Switch>

isTokenVerifiedis a method call to check the authorization token basically it returns boolean.

isTokenVerified是一个检查授权令牌的方法调用,基本上它返回布尔值。