如何设计一个基于层次角色的访问控制系统

46

基本想法是,我们为项目建立了自定义的“启动程序”。为此,我们正在重新设计用户控制。我知道有很多关于通用RBAC的问题,但我找不到任何关于分层RBAC的问题?

我们的要求是:

  • 角色可以分配给组权限
  • 如果角色没有权限条目,则自动拒绝
  • 用户可以被赋予超级权限
  • 用户的超级权限是授予或拒绝
  • 如果用户被明确拒绝某个权限,无论角色如何说“允许”,覆盖都会生效。
  • 用户可以具有多个角色
  • 角色可以具有层次结构
  • 角色可以从其他角色继承(例如,“论坛超级版主”角色是“论坛版主”和“系统维护人员”,而“论坛版主”角色已经继承了“论坛用户”角色)
  • 继承另一个角色并拒绝或授予特权的角色将覆盖其子权限
  • 权限按“模块”分组(例如,“博客”模块可以有“编辑条目”权限,而“论坛”模块可以有“编辑条目”权限,它们不会冲突)
  • 有一个“一切和任何事物”的权限,自动授予完全访问权限

所以,在这些要求得到满足后,以下是我对它的想法。

表:用户

id            | int     | unique id

表格:角色

id            | int     | unique id
--------------|---------------------------------------------
title         | varchar | human readable name

表格:权限

id            | int     | unique id
--------------|---------------------------------------------
module        | varchar | module name
--------------|---------------------------------------------
title         | varchar | human readable name
--------------|---------------------------------------------
key           | varchar | key name used in functions

表格:Role_User

role_id       | int     | id from roles table
--------------|---------------------------------------------
user_id       | int     | id from users table

表格:Permission_Role

id            | int     | unique id
--------------|---------------------------------------------
permission_id | int     | id from permissions table
--------------|---------------------------------------------
role_id       | int     | id from roles table
--------------|---------------------------------------------
grant         | tinyint | 0 = deny, 1 = grant

表格:Permission_User

id            | int     | unique id
--------------|---------------------------------------------
permission_id | int     | id from permissions table
--------------|---------------------------------------------
user_id       | int     | id from users table
--------------|---------------------------------------------
grant         | tinyint | 0 = deny, 1 = grant

实际上这只是其中一半,我确定的那部分。我遇到困难的是层级角色。

那么,我该如何设计呢? 我的想法是,在登录时建立权限矩阵并将其保存到会话中,以节省数据库查询次数,因此每次登录只需要运行一次简单的查询。

我看到的问题是,我需要知道角色的层级结构,以便在解决继承之前解决继承的角色权限。

用户权限是容易的部分,每个用户的权限实际上是最终确认的组。


user 没有 role,但是有 permission,这是有原因的吗?这是“权限模型”,而不是“角色模型”,对吗?采用这种方法,角色没有被使用。 - BlitZ
糟糕,忘记添加那个表格了,已经编辑过了! - Hailwood
6
我认为user没有拥有permission的理由,它可以拥有带有permissionrole,但不能直接拥有permission。这会破坏逻辑(依我看)。 - BlitZ
1
@CORRUPT,用户本身也可以被授予权限,这些权限将覆盖他们所在的任何角色。这样可以避免因为想让用户X执行一个额外操作而创建全新的角色。或者如果用户一直在做不应该做的事情,您可以快速地拒绝该用户。否则,您将不得不继承最高级别的角色,然后在那里拒绝,这可能会变得混乱。 - Hailwood
2
这就像是说每个用户都是自己的角色,自动继承了用户所拥有的所有其他角色,而无需为用户创建组。这样说通了吗? - Hailwood
微调需求使记录永远不会拒绝,只能以添加方式授予权限将简化一切 - 强制更好地建模角色层次结构,特别是最受限制的角色,并简化权限查找查询(使用 EXISTS)。默认拒绝所有并在此基础上构建是一种推荐方法。 (来源:https://owasp.org/Top10/A01_2021-Broken_Access_Control) - Epigene
1个回答

64

通过在表 Roles 上使用递归关系实现角色继承的方法是,使角色引用另一条记录:

1:n inheritance

该关系会在 Roles 记录中添加 1 : n 继承。你可以通过这个存储函数获取整个层次结构树:

CREATE FUNCTION `getHierarchy`(`aRole` BIGINT UNSIGNED)
RETURNS VARCHAR(1024)
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE `aResult` VARCHAR(1024) DEFAULT NULL;
DECLARE `aParent` BIGINT UNSIGNED;

SET `aParent` = (SELECT `parent` FROM `Roles` WHERE `id` = `aRole`);

WHILE NOT `aParent` IS NULL DO

    SET `aResult` = CONCAT_WS(',', `aResult`, `aParent`);
    SET `aParent` = (SELECT `parent` FROM `Roles` WHERE `id` = `aParent`);

END WHILE;

RETURN IFNULL(`aResult`, '');
END

然后,你可以像这样获得所有授予的权限:

SELECT
    `permission_id`
FROM
    `Permission_Role`
WHERE
    FIND_IN_SET(`role_id`, `getHierarchy`({$role}))
    AND
    grant;

如果不够的话,您可以为继承再创建另一个表:

n:m inheritance

但在这种情况下,需要使用另一个层次结构获取算法。


为了解决覆盖问题,您需要获取角色权限和用户权限。然后将user的权限写入roles的权限以便存储在session中。


此外,我建议在Permission_RolePermission_User中删除grant列。没有必要为每个用户或角色映射每个权限。只需使用EXISTS查询:如果存在记录,则授予权限;否则,无权访问。如果您需要检索所有权限和状态,则可以使用LEFT JOIN


这听起来不错,我会研究一下的。我肯定会考虑让每个角色继承多个其他角色,所以还需要解决其他算法问题,同时也在思考如果我们无法创建存储过程该怎么办?授予权限或拒绝权限的重点不是列出每个权限,而是如果一个角色继承了另一个角色,如果已经授予了该权限,则可以在继承角色中显式地拒绝它以拒绝该权限。 - Hailwood
针对以上评论,我们可以删除记录来明确拒绝权限,不是吗? - coretechie
1
@coretechie 是的。或者,您可以将“grant”设置为“false”。还要注意,自此回答以来已经过了一段时间,MySQL终于支持递归查询了。现在使用递归选择数据可能更加优化。目前不需要存储过程来实现这样的逻辑。 - BlitZ
有人实现过这个吗?GitHub 上有吗? - Chef Gladiator
@ChefGladiator 当时这是一个不错的解决方案,但现在MySQL已经支持递归查询了。我建议你去那里寻找更强大的方法,而不需要存储过程的复杂性。对于现成的组件我不确定,我猜其中一些取决于目标生态系统(php、python等)。考虑检查具有RBAC功能的软件包,有很多这样的软件包。 - BlitZ

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