我在React和Redux中编写了一个单页面应用程序(带有Node.js后端)。
我想实现基于角色的访问控制,并希望控制应用程序的某些部分(或子部分)的显示。
我将从Node.js获取权限列表,这只是一个具有这种结构的对象:
{
users: 'read',
models: 'write',
...
dictionaries: 'none',
}
密钥是受保护资源,
值是此资源的用户权限(none
,read
,write
之一。
我将它存储到redux状态。似乎很容易。
none
权限将由react-router
路由onEnter/onChange
个网址或redux-auth-wrapper
进行检查。这似乎也很容易。
但是,将read/write
权限应用于任何组件视图的最佳方式是什么(例如,如果用户具有{ models: 'read' }
权限,则隐藏模型组件中的编辑按钮。)
我找到this solution并为我的任务更改了一点:
class Check extends React.Component {
static propTypes = {
resource: React.PropTypes.string.isRequired,
permission: React.PropTypes.oneOf(['read', 'write']),
userPermissions: React.PropTypes.object,
};
// Checks that user permission for resource is the same or greater than required
allowed() {
const permissions = ['read', 'write'];
const { permission, userPermissions } = this.props;
const userPermission = userPermissions[resource] || 'none';
return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
}
render() {
if (this.allowed()) return { this.props.children };
}
}
export default connect(userPermissionsSelector)(Check)
其中userPermissionsSelector
将是这样的:(store) => store.userPermisisons
并返回用户权限对象。
然后用Check
包裹受保护的元素:
<Check resource="models" permission="write">
<Button>Edit model</Button>
</Check>
因此,如果用户没有write
models
权限,则该按钮将不会显示。
有人做过这样的事吗?是否有比此更“优雅”的解决方案?
谢谢!
P.S。当然也会在服务器端检查用户权限。
答案 0 :(得分:7)
嗯,我想我明白了你的意思。我已经做了一些适合我的事情,我喜欢我的方式,但我知道其他可行的解决方案都在那里。
我写的是HOC反应路由器风格。
基本上我有 PermissionsProvider ,我在其中初始化用户权限。我有另一个 withPermissions HOC,它将我之前提供的权限注入到我的组件中。
因此,如果我需要检查该特定组件的权限,我可以轻松访问它们。
// PermissionsProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
class PermissionsProvider extends React.Component {
static propTypes = {
permissions: PropTypes.array.isRequired,
};
static contextTypes = {
permissions: PropTypes.array,
};
static childContextTypes = {
permissions: PropTypes.array.isRequired,
};
getChildContext() {
// maybe you want to transform the permissions somehow
// maybe run them through some helpers. situational stuff
// otherwise just return the object with the props.permissions
// const permissions = doSomething(this.props.permissions);
// maybe add some validation methods
return { permissions: this.props.permissions };
}
render() {
return React.Children.only(this.props.children);
}
}
const withPermissions = Component => {
const C = (props, context) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} />
);
};
C.displayName = `withPermissions(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
C.contextTypes = {
permissions: PropTypes.array.isRequired
};
return hoistStatics(C, Component);
};
export { PermissionsProvider as default, withPermissions };
好的,我知道这是很多代码。但这些是HOC(你可以了解更多here)。
高阶分量(HOC)是React for中的一种高级技术 重用组件逻辑。 HOC本身不是React API的一部分。 它们是React组成性质的一种模式。 具体地说,高阶分量是一个需要a的函数 组件并返回一个新组件。
基本上我这样做是因为我受到路由器反应的启发。 每当你想知道一些路由的东西,你可以添加装饰器 @withRouter ,他们将道具注入你的组件。 那么为什么不做同样的事情呢?
//App render
return (
<PermissionsProvider permissions={permissions}>
<SomeStuff />
</PermissionsProvider>
);
SomeStuff 内的某个地方,您有一个广泛使用的工具栏来检查权限?
@withPermissions
export default class Toolbar extends React.Component {
render() {
const { permissions } = this.props;
return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />;
}
}
如果您不能使用装饰器,则可以像这样导出工具栏
export default withPermissions(Toolbar);
这是我在实践中展示的代码框:
https://codesandbox.io/s/lxor8v3pkz
注意:
答案 1 :(得分:1)
@lokuzt建议的方法很棒。
您甚至可以进一步简化代码。
首先,每个受保护的组件都需要满足某些要求才能进行渲染。您需要定义一个函数,该函数以要求进行呈现,并以凭据作为当前用户的参数。它必须返回 true 或 false 。
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
此外,我们必须使用ReactJS中的HOC (Higher-Order Component)来定义new context API。
const { Provider, Consumer } = React.createContext();
function protect(requirement, WrappedComponent) {
return class extends Component {
render() {
return (
<Consumer>
{ credentials => isSatisfied(requirement, credentials)
? <WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>
: null
}
</Consumer>
);
}
};
}
现在您可以装饰您的组件了:
const requireAdmin = {...}; // <- this is your requirement
class AdminPanel extends Component {
...
}
export default protect(requireAdmin, AdminPanel);
甚至第三方组件:
import {Button} from 'react-bootstrap';
const AdminButton = protect(requireAdmin, Button);
ReactJS上下文API必须传递凭据:
class MyApp extends Component {
render() {
const {credentials} = this.props;
<Provider value={credentials}>
...
<AdminPanel/>
<AdminButton>
Drop Database
</AdminButton>
...
</Provider>
}
}
这是我在github上的扩展implementation。
demo也可用。