[转载]一种高性能Hierarchical RBAC实现方案

背景算法

 

框图数据库

 

上图中,Role和被设置Permission的Resource都是能够有任意层级继承关系的。性能

 

举例网站

 

举一个网站的例子来讲:orm

 

若是,User表示网站用户;Role表示角色;Resource表示全部可访问的URL;Permission是对每个URL的某一个权限(如:查看,修改等)。blog

 

Role能够有任意层级继承关系,如:用户角色能够分为Normal User和Admin User,Admin User下又能够分为Super Admin、Content Admin、Support Admin等。继承

 

URL这种Resource也能够有任意层级继承关系的,如:对http://abc.com/A/A1/A11/A111.aspx这样一个连接,能够认为http://abc.com/A1是一个URL Resource,http://abc.com/A/A1/A11是他的一个子Resource,http://abc.com/A/A1/A11/A111.aspx又是http://abc.com/A/A1/A11的一个子Resource。递归

 

对某一个Role来讲,他对某一个Resource – R1的具体的Permissions,等于关联到这个Role的Resource - R1及其全部父级Resource的Permissions的并集。索引

 

对于某一个User来讲,他对某一个Resource的Permissions,等于他所属的全部Roles的Permissions的并集。get

 

问题

 

各元素之间的关系容易理解,关键的难点在于,由于Role和Resource均可以是有无限层级继承关系的,如何保证权限信息验证具备较高的性能呢?当继承关系较复杂时,递归检测的性能无疑是不可接受的。

 

数据库表

 

User(ID,Name)

Role(ID,Name,ParentID,LeftIndex,RightIndex

UsersInRoles(UserID,RoleID)

Resource(ID,Name,ParentID,LeftIndex,RightIndex

PermissionsOfRole(PermissionsValue,ResourceID,RoleID)

 

这里简单起见,对于Permissions,使用一个二进制位表示一个具体的Permission。咱们须要事先定义一个PermissionsValue的每个二进制位表示的Permission。例如:若是PermissionsValue的二进制值为10101010,表示从低位到高位第二、四、六、8位所表明的权限的并集。

 

使用二进制位表示一个具体的Permission的好处是,处理Permissions的并操做能够转换为二进制的OR;缺点是,具体的Permission想不能特别多,由于多一个就意味着PermissionsValue的最大值大一个2的次方。8位二进制的最大值是2的8次方,这不算很大,可是,1000位二进制的最大值是2的1000次方,这就是个不可想象的巨大数字了。好在,通常来说,具体的Permission项目不会特别多的。

 

该方案的关键,就在于Role和Resource表的LeftIndex和RightIndex这两个字段了,咱们将使用这两个字段,在避免递归的状况下,实现较高性能的取某个继承节点的全部子元素或全部父元素的算法。

 

算法

 

咱们以Role为例,首先Role表中有且只有一条记录存放全部Roles的顶层父节点(1,“Root Role”,1,2)。当他没有子节点时,其LeftIndex和RightIndex的值分别为1和2。当对其插入子节点时,LeftIndex和RightIndex的值须要作相应的调整,调整的规则以下(括号中为LeftIndex和RightIndex的值):

 

按逆时针方向,你们能看出规则吗?

按照这个规则,咱们能够以下获取某一个节点的全部字节点或全部父结点(使用伪SQL代码表示):

 

获取ID为3的Role节点的全部的子结点包括自己:

SELECT * FROM Role WHERE

LeftIndex >= (SELECT LeftIndex FROM Role WHERE ID = 3)

AND

RightIndex <= (SELECT RightIndex FROM Role WHERE ID = 3)

注:若是要不包括ID=3的节点自己,只须要用>和<代替>=和<=。

 

获取ID为5的Role节点的全部父节点包括自己:

SELECT * FROM Role WHERE

LeftIndex <= (SELECT LeftIndex FROM Role WHERE ID = 3)

AND

RightIndex >= (SELECT RightIndex FROM Role WHERE ID = 3)

注:若是要不包括ID=5的节点自己,只须要用>和<代替>=和<=。

 

你们能够根据上面的图验证一下算法的效果。彻底不须要递归,只须要简单的判断LeftIndex和RightIndex就行,性能天然是很是好的。

 

咱们甚至能够以很是简单的SQL语句得到某一个ID为2的User对ID为6的Resource的PermissionsValue:

 

DECLARE @PermissionsValue int;

SELECT @PermissionsValue = @PermissionsValue | PermissionsValue

FROM PermissionsOfRole WHERE

RoleID IN

(

SELECT ID FROM Role WHERE

LeftIndex >= (SELECT LeftIndex FROM Role WHERE ID IN

(SELECT RoleID FROM UsersInRoles WHERE UserID = 2))

AND

RightIndex <= (SELECT RightIndex FROM Role WHERE ID IN

(SELECT RoleID FROM UsersInRoles WHERE UserID = 2))

)

AND

ResourceID IN

(

SELECT ID FROM Resource WHERE

LeftIndex <= (SELECT LeftIndex FROM Resource WHERE ID = 6)

AND

RightIndex >= (SELECT RightIndex FROM Resource WHERE ID = 6)

);

SELECT @PermissionsValue;

上面的SQL虽然有很多嵌套的SELECT,可是,由于子查询基本上都是对主键字段的条件判断,LeftIndex和RightIndex咱们也会加上索引,所以,实际上不会对性能形成太大影响。

 

OK,查询性能很好,不过这是以新建或修改Role和Resource的层级关系时的必定的性能损失为代价的。每次新增或修改Role或Resource的层级关系时,必须按照前面所述的规则重置全部节点的LeftIndex和RightIndex值。不过,通常状况下,因为Role和Resource的维护操做占系统总体操做的比例很小,几乎能够忽略,所以其性能损失也不是什么大问题。具体的重置全部节点LeftIndex和RightIndex值的伪代码我就不贴出来了,你们稍微花费几个脑细胞就能想出来了^-^

//结束

相关文章
相关标签/搜索