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

IT技术 javascript reactjs react-router react-router-v4
2021-01-17 01:31:35

我试图实现经过身份验证的路由,但发现 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>

错误是:

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

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

它出现在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>

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


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

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
}

在 中分派动作是否正确render()感觉不对。使用componentDidMount或其他一些钩子似乎也不正确

6个回答

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

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

现在你的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} />

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

我不知道为什么,但添加state: {from: props.location}}}导致maximum call stack exceeded. 我不得不删除它。你能解释一下为什么这个选项有用吗@Tyler McGinnis?
2021-03-22 01:31:35
我坚持我的道具authedredux-persist,这时候true还是引起了一时的重定向"/login"什么本质上发生的是,在以路线重载或刷新"/dashboard'authed === true Dashboard组件呈现然后路由变化"/login"导致"Login"组件呈现,最后的路线变回/dashboard路线和"Dashboard"组件呈现。这可能是什么原因?我检查redux-devtoolsauthed道具是true这些路线变化发生的整个时间。@泰勒麦金尼斯。
2021-03-23 01:31:35
哦,这类似于我的解决方案,但它使用<Redirect />. 问题是<Redirect />在我的情况下似乎不适用于 redux?我需要发送一个动作
2021-03-24 01:31:35
@TylerMcGinnis 如果我们需要使用渲染函数将道具传递给组件怎么办?
2021-03-30 01:31:35
@faraz 这解释了({component: Component, ...rest})语法。我有同样的问题哈哈!stackoverflow.com/a/43484565/6502003
2021-04-09 01:31:35

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

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

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

你可以像这样实现

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

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

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

(使用 Redux 进行状态管理)

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

将解码的授权有效负载数据也存储在 redux 状态并将其传递给 react 上下文。(我们不必使用上下文,而是在任何嵌套的子组件中访问授权,这使得从上下文访问变得容易,而不是将每个子组件连接到 redux)。

所有不需要特殊角色的路由都可以在登录后直接访问..如果它需要像admin这样的角色(我们做了一个受保护的路由,如果没有重定向到未经授权的组件,它会检查他是否有想要的角色)

如果您必须根据角色禁用按钮或某些内容,则在您的任何组件中类似。

只是你可以这样做

const authorization = useContext(AuthContext);
const [hasAdminRole] = checkAuth({authorization, roleType:"admin"});
const [hasLeadRole] = checkAuth({authorization, roleType:"lead"});
<Button disable={!hasAdminRole} />Admin can access</Button>
<Button disable={!hasLeadRole || !hasAdminRole} />admin or lead can access</Button>

那么如果用户尝试在 localstorage 中插入虚拟令牌怎么办。由于我们有访问令牌,我们将重定向到 home 组件。我的 home 组件将进行 rest 调用以获取数据,因为 jwt 令牌是虚拟的,rest 调用将返回未经授权的用户。所以我确实调用了注销(这将清除本地存储并再次重定向到登录页面)。如果主页有静态数据并且没有进行任何 api 调用(那么您应该在后端进行令牌验证 api 调用,以便您可以在加载主页之前检查令牌是否为 REAL)

索引.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')
);

历史.js

import { createBrowserHistory as history } from 'history';

export default history({});

私有路由.js

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

const ProtectedRoute = ({ component: Component, roleType, ...rest })=> { 
const authorization = useContext(AuthContext);
const [hasRequiredRole] = checkAuth({authorization, roleType});
return (
<Route
  {...rest}
  render={props => hasRequiredRole ? 
  <Component {...props} /> :
   <Unauthorized {...props} />  } 
/>)}; 

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

// my user reducer sample
// const accessToken = localStorage.getItem('token')
//   ? JSON.parse(localStorage.getItem('token')).accessToken
//   : false;

// const initialState = {
//   accessToken: accessToken ? accessToken : null,
//   authorization: accessToken
//     ? jwtDecode(JSON.parse(localStorage.getItem('token')).accessToken)
//         .authorization
//     : null
// };

// export default function(state = initialState, action) {
// switch (action.type) {
// case actionTypes.FETCH_LOGIN_SUCCESS:
//   let token = {
//                  accessToken: action.payload.token
//               };
//   localStorage.setItem('token', JSON.stringify(token))
//   return {
//     ...state,
//     accessToken: action.payload.token,
//     authorization: jwtDecode(action.payload.token).authorization
//   };
//    default:
//         return state;
//    }
//    }

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

export default connect(mapStateToProps)(Privateroutes);

checkAuth.js

import React from 'react';

export const AuthContext = React.createContext();

export const checkAuth = ({ authorization, roleType }) => {
  let hasRequiredRole = false;

  if (authorization.roles ) {
    let roles = authorization.roles.map(item =>
      item.toLowerCase()
    );

    hasRequiredRole = roles.includes(roleType);
  }

  return [hasRequiredRole];
};

解码的 JWT 代币样本

{
  "authorization": {
    "roles": [
      "admin",
      "operator"
    ]
  },
  "exp": 1591733170,
  "user_id": 1,
  "orig_iat": 1591646770,
  "email": "hemanthvrm@stackoverflow",
  "username": "hemanthvrm"
}
以及如何处理对 的直接访问Signin如果用户知道他没有登录,他应该可以选择直接访问登录,对吗?
2021-03-17 01:31:35
对于那些使用 redux 的人,我确实有一个更好的版本……几天后我会更新我的答案……谢谢 –
2021-03-22 01:31:35
@carkod...默认情况下,如果他尝试访问任何路由,他将被重定向到登录页面...(因为他没有令牌)
2021-04-04 01:31:35
@carkod .. 一旦用户点击注销,否则我的 jwt 刷新令牌过期..我确实调用注销功能,我清除 localstorage 和刷新窗口......因此 localstorage 不会有令牌..它会自动重定向到登录页面
2021-04-07 01:31:35

所有答案都已过时

在 2021 年,根据 react-router-dom 文档组件renderpropRoute用于遗留用途,在我的情况下,它甚至不起作用(我使用的是 react-router-dom 5.2.0)。

这可以代替

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

const RouteRequiresLogin: FC<React.ComponentProps<typeof Route>> = props => {
   const userIsLogged = useLoginStatus();

   return (
      <Route {...props}>{userIsLogged ? props.children : <LoginPage/>}</Route>
   );
};

export default RouteRequiresLogin;

用法:

/* A route that requires login */
<RouteRequiresLogin path="/dashboard">
   <DashboardPage />
</RouteRequiresLogin>

/* A route that doesn't require login */
<Route path="/sign-up">
   <SignUpPage />
</Route>
useLoginStatus() 是否仅作为获取当前登录状态的行的示例
2021-03-17 01:31:35
这太棒了,为我节省了很多时间!
2021-03-29 01:31:35
useLoginStatus() 除了这个,我们也可以通过本地存储检查登录状态,对吗?
2021-04-06 01:31:35

安装 react-router-dom

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

在 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;
每条路线?这不会扩展。
2021-03-14 01:31:35