zoukankan      html  css  js  c++  java
  • 一种高性能Hierarchical RBAC实现方案

    背景

     

    框图

    h_rbac.GIF

    上图中,Role和被设置PermissionResource都是可以有任意层级继承关系的。

     

    举例

     

    举一个网站的例子来说:

     

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

     

    Role可以有任意层级继承关系,如:用户角色可以分为Normal UserAdmin UserAdmin User下又可以分为Super AdminContent AdminSupport Admin等。

     

    URL这种Resource也可以有任意层级继承关系的,如:对http://abc.com/A/A1/A11/A111.aspx这样一个链接,可以认为http://abc.com/A1是一个URL Resourcehttp://abc.com/A/A1/A11是他的一个子Resourcehttp://abc.com/A/A1/A11/A111.aspx又是http://abc.com/A/A1/A11的一个子Resource

     

    对某一个Role来说,他对某一个Resource – R1的具体的Permissions,等于关联到这个RoleResource - R1及其所有父级ResourcePermissions的并集。

     

    对于某一个User来说,他对某一个ResourcePermissions,等于他所属的所有RolesPermissions的并集。

     

    问题

     

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

     

    数据库表

     

    UserIDName

    RoleIDNameParentIDLeftIndexRightIndex

    UsersInRolesUserIDRoleID

    ResourceIDNameParentIDLeftIndexRightIndex

    PermissionsOfRolePermissionsValueResourceIDRoleID

     

    这里简单起见,对于Permissions,使用一个二进制位表示一个具体的Permission。我们需要事先定义一个PermissionsValue的每一个二进制位表示的Permission。例如:如果PermissionsValue的二进制值为10101010,表示从低位到高位第2468位所代表的权限的并集。

     

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

     

    该方案的关键,就在于RoleResource表的LeftIndexRightIndex这两个字段了,我们将使用这两个字段,在避免递归的情况下,实现较高性能的取某个继承节点的所有子元素或所有父元素的算法。

     

    算法

     

    我们以Role为例,首先Role表中有且只有一条记录存放所有Roles的顶层父节点(1,“Root Role”,12)。当他没有子节点时,其LeftIndexRightIndex的值分别为12。当对其插入子节点时,LeftIndexRightIndex的值需要做相应的调整,调整的规则如下(括号中为LeftIndexRightIndex的值):

    h_rbac_alg.GIF

    按逆时针方向,大家能看出规则吗?

    按照这个规则,我们可以如下获取某一个节点的所有字节点或所有父结点(使用伪SQL代码表示):

     

    获取ID3Role节点的所有的子结点包括本身:

    SELECT * FROM Role WHERE

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

    AND

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

    注:如果要不包括ID=3的节点本身,只需要用><代替>=<=

     

    获取ID5Role节点的所有父节点包括本身:

    SELECT * FROM Role WHERE

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

    AND

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

    注:如果要不包括ID=5的节点本身,只需要用><代替>=<=

     

    大家可以根据上面的图验证一下算法的效果。完全不需要递归,只需要简单的判断LeftIndexRightIndex就行,性能自然是非常好的。

     

    我们甚至可以以非常简单的SQL语句获得某一个ID2UserID6ResourcePermissionsValue

     

    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,但是,因为子查询基本上都是对主键字段的条件判断,LeftIndexRightIndex我们也会加上索引,因此,实际上不会对性能造成太大影响。

     

    OK,查询性能很好,不过这是以新建或修改RoleResource的层级关系时的一定的性能损失为代价的。每次新增或修改RoleResource的层级关系时,必须按照前面所述的规则重置所有节点的LeftIndexRightIndex值。不过,一般情况下,由于RoleResource的维护操作占系统整体操作的比例很小,几乎可以忽略,因此其性能损失也不是什么大问题。具体的重置所有节点LeftIndexRightIndex值的伪代码我就不贴出来了,大家稍微花费几个脑细胞就能想出来了^-^

    //结束

  • 相关阅读:
    前端之JQuery
    前端之BOM和DOM
    前端之js
    Django之创建项目、目录层级、基本操作
    前端小练习-Michael的博客界面(粗糙版)
    前端之CSS(下)
    Django简介(MVC、MTV)
    react中自定义函数、生命周期钩子函数、修改状态、组件、组件传值
    react中类组件、函数组件、state、单层遍历、多层遍历、先遍历后渲染、if-else、三目运算符
    搭建react的架手架
  • 原文地址:https://www.cnblogs.com/teddyma/p/1050627.html
Copyright © 2011-2022 走看看