如何在反应路由器中限制对路由的访问?

有人知道如何在反应路由器中限制对特定路由的访问吗?在允许访问特定路由之前,我想检查用户是否已登录。我以为会很简单,但是医生不清楚怎么做。

这是我应该在定义 <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>
126749 次浏览

usually a logged in user will be granted a token, and uses this token for any communication with server. What we usually do is define a root page, and things build on top of that page. this root page does localisation, authentication and other configurations for you.

here's an example

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>
);

on your root page, check for token null or authenticate the token with server to see if user is valid login.

hope this helps :)

If you want to use authentication across your whole application, you need to store some data application-wide (e.g. token). You can set up two React mixins that are responsible for managing $auth object. This object shouldn't be available outside those two mixins. Here's example of that:

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();
}
}
}
}
};
});

Then you can just mixin Authenticator mixing to your login components (login screen, login popup, etc) and call this.login function when you have all the data necessary.

The most important thing is protecting your components by mixing in NeedsAuthenticatedUser mixin. Each component that needs authenticated user will have to look like that:

var um = require('userManagement');


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

Note that NeedsAuthenticatedUser uses react-router API (willTransitionTo and transition.abort()).

Update (Aug 16, 2019)

In react-router v4 and using React Hooks this looks a little different. Let's start with your 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>
);
}

We are using an Auth library to check if the user is currently authenticated. Replace this with your auth check function. If so then we set the isAuthenticated flag to true. We do this when our App first loads. Also worth mentioning, you might want to add a loading sign on your app while the auth check is being run, so you don't flash the login page every time you refresh the page.

Then we pass the flag to our routes. We create two type of routes AuthenticatedRoute and UnauthenticatedRoute.

The AuthenticatedRoute.js looks like this.

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}`}
/>}
/>
);
}

It checks if isAuthenticated is set to true. If it is, then it'll render the desired component. If not, then it'll redirect to the login page.

The UnauthenticatedRoute.js on the other hand looks like this.

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

In this case, if the isAuthenticated is set to false, it'll render the desired component. And if it is set to true, it'll send you to the homepage.

You can find detailed versions of this on our guide - https://serverless-stack.com/chapters/create-a-route-that-redirects.html.

Older version

The accepted answer is correct but Mixins are considered to be harmful (https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html) by the React team.

If somebody comes across this question and is looking for the recommended way to do this, I'd suggest using Higher Order Components instead of Mixins.

Here is an example of a HOC that'll check if the user is logged in before proceeding. And if the user is not logged in, then it'll redirect you to the login page. This component takes a prop called isLoggedIn, that is basically a flag that your application can store to denote if the user is logged in.

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);
}

And to use this HOC, just wrap it around your routes. In case of your example, it would be:

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

I cover this and a few other topics in a detailed step-by-step tutorial here - https://serverless-stack.com/chapters/create-a-hoc-that-checks-auth.html

react-router encourages a declarative approach for your router, you should make your router as dumb as possible and avoid putting your routing logic in your components.

Here is how you can do it (assuming you pass it the loggedIn prop):

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

There is (now?) an example of this in React Router 4's docs for Redirect

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

Usage:

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

Based on https://tylermcginnis.com/react-router-protected-routes-authentication/.

You can use HOC and auth is a variable you can change value true or false means(authorization)

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

You can avoid to render component before confirming authentication, like as below:

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

Or simply, yarn add react-routers, which component have props beforeEach, beforeRoute like Vue Route.