如何在React Redux应用中实现基于角色的限制/权限?

24

我有一个使用React-Redux-KoaJs开发的应用程序,其中包含多个组件。我还拥有几种用户角色。现在,我想仅向特定角色显示一些按钮、表格和div,并隐藏其他角色的这些内容。请注意,我不想隐藏整个组件,而只是其中的一部分。有人能帮我吗?先谢谢了。

7个回答

32

您可以像 @Eudald Arranz 建议的那样,在每个组件中检查角色或权限。或者,您可以编写一个组件来为您检查权限。例如:

import PropTypes from 'prop-types';
import { connect } from 'react-redux';

const ShowForPermissionComponent = (props) => {
    const couldShow = props.userPermissions.includes(props.permission);
    return couldShow ? props.children : null;
};

ShowForPermissionComponent.propTypes = {
    permission: PropTypes.string.isRequired,
    userPermissions: PropTypes.array.isRequired
};


const mapStateToProps = state => ({
    userPermissions: state.user.permission //<--- here you will get permissions for your user from Redux store
});

export const ShowForPermission = connect(mapStateToProps)(ShowForPermissionComponent);

然后你可以像这样使用这个组件:

import React from 'react';
import { ShowForPermission } from './ShowForPermission';

cons MyComponent = props => {
   return (
        <div>
            <ShowForPermission permission="DELETE">
                <button>Delete</button>
            </ShowForPermission>
        </div>
   );
}


谢谢您的答案。我非常感激。但是我找到了一种替代方法,我很快会在答案中提供。请检查一下。欢迎您的建议。 - Harshit Agarwal
4
这种方法存在性能缺陷。想象一下,你的页面中有150多个或更多需要检查权限的UI元素(按钮、选择框、复选框、菜单选项、选项卡等等)。使用这种方法,您需要调用props.userPermissions.includes方法150多次或更多次。您为每个元素迭代相同的数组。这可能会减慢您的应用程序。 - Green
1
@Green 是的,你说得对。你可以将你的权限保存在 ES6 的 Set 中,或者如果你不喜欢使用可变集合,你可以使用 immutable.js 中的 Set。或者你也可以只使用一个 Object,其中键是权限名称,值是任何你想要的东西。 - Andrii Golubenko

16

注意这一点。如果某些角色的操作很重要,您应该始终在后端对它们进行验证。如果没有适当的验证,很容易在前端更改redux中存储的值,从而允许对角色进行恶意使用。

如果您想采取可能的方法,请按照以下步骤:

  • 将角色保存到您的reducer中
  • 将reducer绑定到组件:
function mapStateToProps(state) {
  const { user_roles } = state;
  return { user_roles };
}

export default connect(mapStateToProps)(YourComponent);
  • 然后在您的组件中,您可以检查用户角色并相应地呈现操作:
render() {
    return (
      <div>
        {this.props.user_roles.role === "YOUR_ROLE_TO_CHECK" && <ActionsComponent />}
      </div>
    );
  }

只有在角色等于所需角色时,ActionsComponent 才会呈现。

再次强调,始终要在后端验证角色!


2
感谢您的回答。这是一种可行的方法,但如果您有很多组件和元素需要显示和隐藏,那么跟踪它们就变得非常困难。我已经想出了一种替代方法,很快就会在答案中发布。请查看并提出您的建议。 - Harshit Agarwal
3
@Harshit,现在已经过去3年了。你曾经承诺很快提供另一种方法,它在哪里呢?请提供翻译的内容。 - Simple Fellow

12

解决此问题的最佳做法是,简单地防止应用程序生成不必要的路由。而不是在每个路由上检查当前用户角色,生成只有用户有访问权限的路由。

So The Normal reouting is: To control the whole view:

const App = () => (
  <BrowserRouter history={history}>
    <Switch>
      <Route path="/Account" component={PrivateAccount} />
      <Route path="/Home" component={Home} />
    </Switch>
  </BrowserRouter>
  export default App;
);

基于用户角色的路由:

import { connect } from 'react-redux'
   // other imports ...
   const App = () => (
      <BrowserRouter history={history}>
        <Switch>
        {
          this.props.currentUser.role === 'admin' ?
            <>
          <Route path="/Account" exact component={PrivateAccount} />
          <Route path="/Home" exact component={Home} />
            </> 
            : 
          <Route path="/Home" exact component={Home} />
        }
        <Route component={fourOFourErroPage} />

        </Switch>
      </BrowserRouter>
      
const mapStateToProps = (state) => {
  return {
    currentUser: state.currentUser,
  }
}
export default connect(mapStateToProps)(App);

管理员用户将可以访问“账户”页面,其他用户只能访问“主页”,如果任何用户尝试访问其他页面,则会出现404页面错误。

希望这个解决方案对你有所帮助。

有关此方法的详细信息,请查看GitHub上的此存储库:基于角色的访问控制与react

要隐藏仅呈现组件:

{this.props.currentUser.role === 'admin' && <DeleteUser id={this.props.userId} /> }


4
这听起来对于路由来说很不错,但在每个组件中可能都会有针对用户角色显示的内容。 - Harshit Agarwal
1
没错,你可以像上面提到的那样隐藏表示组件。 - Nbenz
这个回答并不直接相关于所要求的内容 - undefined

6

所以,我已经想出了一种替代而简单的方法,在前端实现基于角色的访问控制(RBAC)。

在你的 redux store 状态中,创建一个名为 permissions 的对象(或者你可以随意取名),像这样:

const InitialState = {
  permissions: {}
};

然后在您的登录操作中,按照以下方式设置要提供的权限:
InitialState['permissions'] ={
  canViewProfile: (role!=='visitor'),
  canDeleteUser: (role === 'coordinator' || role === 'admin')
  // Add more permissions as you like
}

第一个权限表示,如果你不是访客,可以查看个人资料。 第二个权限表示,只有管理员或协调员才能删除用户。 这些变量将根据已登录用户的角色保留 true 或 false。因此,在存储状态中,您将拥有一个权限对象,其中键表示权限,其值将根据您的角色决定。

然后在组件中使用存储状态获取权限对象。您可以使用连接进行此操作,如下所示:

const mapStateToProps = (state) => {
  permissions : state.permissions
}

然后将这些属性连接到您的组件中,如下所示:

export default connect(mapStateToProps,null)(ComponentName);

接下来,您可以在组件内部的任何要有条件显示的特定元素上使用这些属性,就像这样:

{(this.props.permissions.canDeleteUser) && <button onClick={this.deleteUser}>Delete User</button>}

以上代码将确保仅在您具有删除用户的权限时才呈现删除用户按钮,即在您的存储状态权限对象中,canDeleteUser的值为true。

就是这样,您已经应用了基于角色的访问控制。您可以使用此方法,因为它易于扩展和可变,因为您将根据角色拥有所有权限。

希望这有所帮助!如果我漏掉了什么,请在评论中指出。 :-)


5
它并不容易扩展。它只适用于像你这样简单的用例——几个角色和几个权限。如果情况比这更复杂(例如应用程序的不同阶段具有不同的角色权限),则最终会出现a && b || c && d || e 这种难以管理的情况。 - Green
@Green,那么在单页应用程序中管理可扩展用户角色和权限的最佳方法是什么?是否可能与后端同步? - Yehya

1
我已经在rbac-react-redux-aspnetcore repository中实现了这个功能。如果有人想要使用Redux与Context API,那么下面的代码片段可能会有所帮助。
export const SecuedLink = ({ resource, text, url }) => {

  const userContext = useSelector(state => {
    return state.userContext;
  });    

  const isAllowed = checkPermission(resource, userContext);
  const isDisabled = checkIsDisabled(resource, userContext);

  return (isAllowed && <Link className={isDisabled ? "disable-control" : ""} to={() => url}>{text}</Link>)
}


const getElement = (resource, userContext) => {
    return userContext.resources
        && userContext.resources.length > 0
        && userContext.resources.find(element => element.name === resource);
}

export const checkPermission = (resource, userContext) => {
    const element = getElement(resource, userContext);
    return userContext.isAuthenticated && element != null && element.isAllowed;
}

export const checkIsDisabled = (resource, userContext) => {
    const element = getElement(resource, userContext);
    return userContext.isAuthenticated && element != null && element.isDisabled;
}

要使用上面的代码片段,我们可以像下面这样使用它。
  <SecuedLink resource='link-post-edit' url={`/post-edit/${post.id}`} text='Edit'></SecuedLink>
  <SecuedLink resource='link-post-delete' url={`/post-delete/${post.id}`} text='Delete'></SecuedLink>

因此,根据角色,您不仅可以显示/隐藏元素,还可以启用/禁用它们。权限管理完全与react-client解耦,并在数据库中进行管理,这样您就不必一遍又一遍地部署代码以支持新角色和新权限。

你还没有展示权限是如何设置的。 - opticyclic
在服务器端代码中(存储在MS SQL服务器中)。 - Foyzul Karim

0

步骤可以是:

  • 维护一个包含菜单和子菜单列表的json结构。
  • 迭代原始的json结构,对于每个菜单和子菜单
    • 检查已登录用户的角色
      • 累积新的json菜单列表,以供渲染使用。

为了效率,您可以使用react-redux来存储和重用这个新构建的json菜单。

请记住,所有的操作都在客户端进行。您仍然需要为每个API请求实现适当的授权机制,以防止未经授权的访问。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接