如何限制对 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(willTransitionTo
和 transition.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
。我们在应用程序首次加载时执行此操作。另外值得一提的是,您可能希望在进行身份验证时在您的应用程序上添加一个加载标志 运行,这样您就不会在每次刷新页面时都刷新登录页面。
然后我们将标志传递给我们的路线。我们创建两种类型的路由 AuthenticatedRoute
和 UnauthenticatedRoute
.
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} />
];
的 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 beforeEach
,beforeRoute
就像 Vue Route。
有谁知道如何在 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(willTransitionTo
和 transition.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
。我们在应用程序首次加载时执行此操作。另外值得一提的是,您可能希望在进行身份验证时在您的应用程序上添加一个加载标志 运行,这样您就不会在每次刷新页面时都刷新登录页面。
然后我们将标志传递给我们的路线。我们创建两种类型的路由 AuthenticatedRoute
和 UnauthenticatedRoute
.
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} />
];
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 beforeEach
,beforeRoute
就像 Vue Route。