首页 文章

根据用户角色隐藏一些React组件子项

提问于
浏览
7

我在React和Redux中编写了一个单页面应用程序(带有Node.js后端) .

我想实现基于角色的访问控制,并希望控制应用程序的某些部分(或子部分)的显示 .

我将从Node.js获取权限列表,这只是一个具有这种结构的对象:

{
  users: 'read',
  models: 'write',
  ...
  dictionaries: 'none',
}

key 是受保护的资源,

value 是此资源的用户权限(以下之一: nonereadwrite ) .

我将它存储到redux状态 . 似乎很容易 .

将通过 react-router routes onEnter/onChange hooks或 redux-auth-wrapper 检查 none 权限 . 这似乎也很容易 .

但是,将 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>

因此,如果用户没有 modelswrite 权限,则不会显示该按钮 .

有人做过这样的事吗?有比这更“优雅”的解决方案吗?

谢谢!

P.S. 当然也会在服务器端检查用户权限 .

2 回答

  • 6

    @lokuzt建议的方法很棒 .

    您可以更进一步,以简化您的代码 .

    首先,每个受保护的组件都有一些 requirement 满足渲染 . 您需要定义一个函数,该函数将 requirement 呈现并将当前用户的 credentials 作为参数 . 它必须返回 truefalse .

    function isSatisfied(requirement, credentials) {
      if (...) {
        return false;
      }
    
      return true;
    }
    

    此外,我们必须使用ReactJS中的new context API定义HOC (Higher-Order Component) .

    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也可用 .

  • 0

    好吧,我想我明白你想要什么 . 我已经做了一些适合我的事情,我喜欢我的方式,但我知道其他可行的解决方案都在那里 .

    我写的是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中用于重用组件逻辑的高级技术 . HOC本身不是React API的一部分 . 它们是从React的组成性质中产生的模式 . 具体地说,高阶组件是一个获取组件并返回新组件的函数 .

    基本上我这样做是因为我受到路由器反应的启发 . 每当你想知道一些路由的东西,你可以添加装饰器 @withRouter ,他们将道具注入你的组件 . So why not do the same thing?

    • 首先,您必须使用提供程序设置权限

    • 然后在检查权限的组件上使用 withPermissions 装饰器


    //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

    笔记:

    • 我真的真的简化了权限,因为这个逻辑来自你的结束,为了演示的目的我简化了它们 .

    • 我假设权限是一个数组,这就是我检查HOC中的PropTypes.array的原因

    • 它's a really long and complicated answer and I tried to articulate at my best ability. Please don' t烧烤我在这里和那里的一些错误:)

相关问题