React 中的受保护路由与授权
在本课程中,我们将学习如何根据用户的权限对访问某些页面进行限制。
到目前为止,我们应用程序的所有路由对所有人都是开放的,无论用户是否已认证。
例如,当我们从 主页 或 账户 页面登出时,不会进行重定向,尽管这些页面应该仅对已认证的用户开放。没有理由向未认证的用户展示账户或主页,因为这些页面是用户访问敏感信息的地方。
路由保护
在本节中,我们将实现这些路由的保护机制,称为授权。这种保护是一种粗粒度授权,用于检查用户是否已认证。如果未认证,则将用户从一个受保护的路由重定向到一个公共路由;否则,不执行任何操作。
该条件定义如下:
1const condition = authUser => authUser != null; 2 3// 简写形式 4const condition = authUser => !!authUser;
相比之下,更细粒度的授权可以是基于角色或基于权限的授权:
1// 基于角色的授权 2const condition = authUser => authUser.role === 'ADMIN'; 3 4// 基于权限的授权 5const condition = authUser => authUser.permissions.canEditAccount;
幸运的是,我们将以一种灵活的方式实现它,允许我们自由定义授权条件(谓词)。这样,我们可以使用通用的授权规则,例如基于权限和基于角色的授权。
与 withAuthentication 高阶组件类似,这里有一个 withAuthorization 高阶组件,用于将授权业务逻辑与我们的组件分离。它可以应用于需要授权保护的组件(例如主页、账户页面)。
让我们在新的 src/components/Session/withAuthorization.js 文件中添加这个高阶组件:
1import React from 'react'; 2 3const withAuthorization = () => Component => { 4 class WithAuthorization extends React.Component { 5 render() { 6 return <Component {...this.props} />; 7 } 8 } 9 10 return WithAuthorization; 11}; 12 13export default withAuthorization;
到目前为止,这个高阶组件只是接收一个组件作为输入,并将其原样返回。
然而,这个高阶组件应该能够接收一个作为参数传入的条件函数。我们可以决定它是粗粒度(认证)还是细粒度(基于角色、基于权限)的授权规则。
其次,根据条件判断,如果当前页面是受保护的,而用户未通过验证,则应将用户重定向到一个公共页面(公共路由)。
让我们来看一下高阶组件的实现细节,并逐步分析:
1import React from 'react'; 2import { withRouter } from 'react-router-dom'; 3import { compose } from 'recompose'; 4 5import { withFirebase } from '../Firebase'; 6import * as ROUTES from '../../constants/routes'; 7 8const withAuthorization = condition => Component => { 9 class WithAuthorization extends React.Component { 10 componentDidMount() { 11 this.listener = this.props.firebase.auth.onAuthStateChanged( 12 authUser => { 13 if (!condition(authUser)) { 14 this.props.history.push(ROUTES.SIGN_IN); 15 } 16 }, 17 ); 18 } 19 20 componentWillUnmount() { 21 this.listener(); 22 } 23 24 render() { 25 return ( 26 <Component {...this.props} /> 27 ); 28 } 29 } 30 31 return compose( 32 withRouter, 33 withFirebase, 34 )(WithAuthorization); 35}; 36 37export default withAuthorization;
render 方法会渲染传入的组件(例如主页、账户页面),这些组件应由该高阶组件进行保护。我们稍后会进一步优化。
真正的授权逻辑发生在 componentDidMount() 生命周期方法中。与 withAuthentication() 高阶组件一样,它使用 Firebase 监听器,在用户认证状态发生变化时触发回调函数。
认证后的用户是一个 authUser 对象或 null。在这个函数内部,会用 authUser 执行传入的 condition() 函数。
基于授权的重定向
如果授权失败,例如因为认证用户为 null,则高阶组件会将用户重定向到登录页面。
如果授权成功,高阶组件不做任何操作,并渲染传入的组件(例如主页、账户页面)。
为了实现用户重定向,高阶组件通过 React Router 提供的内置 withRouter() 访问了路由器的 history 对象。
在下一课中,我们将把高阶组件导出到 src/components/Sessions/index.js。