背景

框图

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

举例

举一个网站的例子来说:

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

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的并集。

问题

各元素之间的关系容易理解,关键的难点在于,因为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,表示从低位到高位第2、4、6、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值的伪代码我就不贴出来了,大家稍微花费几个脑细胞就能想出来了^-^

//结束

[转载]一种高性能Hierarchical RBAC实现方案的更多相关文章

  1. 0930MySQL中实现高性能高并发计数器方案(例如文章点击数)

    转自http://www.jb51.net/article/56656.htm 这篇文章主要介绍了MySQL中实现高性能高并发计数器方案,本文中的计数器是指如文章的点击数.喜欢数.浏览次数等,需要的朋 ...

  2. 两种高性能 I/O 设计模式 Reactor 和 Proactor

    两种高性能 I/O 设计模式 Reactor 和 Proactor Reactor 和 Proactor 是基于事件驱动,在网络编程中经常用到两种设计模式. 曾经在一个项目中用到了网络库 libeve ...

  3. I/O模型系列之四:两种高性能IO设计模式 Reactor 和 Proactor

    不同的操作系统实现的io策略可能不一样,即使是同一个操作系统也可能存在多重io策略,常见如linux上的select,poll,epoll,面对这么多不同类型的io接口,这里需要一层抽象api来完成, ...

  4. I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  5. 牢记负载均衡与HA,高性能是不同的方案。一般的CLUSTER只能实现其中的一种,而ORACLE的RAC可以有两种。

    F5/LVS<—Haproxy<—Squid/Varnish<—AppServer. 现在网站发展的趋势对网络负载均衡的使用是随着网站规模的提升根据不同的阶段来使用不同的技术: 第一 ...

  6. 两种高性能I/O设计模式(Reactor/Proactor)的比较

    原文出处: Alex Libman   译文出处:潘孙友   欢迎分享原创到伯乐头条 综述 这篇文章探讨并比较两种用于TCP服务器的高性能设计模式. 除了介绍现有的解决方案,还提出了一种更具伸缩性,只 ...

  7. [原创]一种Unity2D多分辨率屏幕适配方案

    此文将阐述一种简单有效的Unity2D多分辨率屏幕适配方案,该方案适用于基于原生开发的Unity2D游戏,即没有使用第三方2D插件,如Uni2D,2D toolkit等开发的游戏,NGUI插件不受这个 ...

  8. 转载 50种方法优化SQL Server数据库查询

    原文地址 http://www.cnblogs.com/zhycyq/articles/2636748.html 50种方法优化SQL Server数据库查询 查询速度慢的原因很多,常见如下几种: 1 ...

  9. <转载> 22种代码味道(Martin Fowler与Kent Beck) http://blog.csdn.net/lovelion/article/details/9301691

    Martin Fowler在Refactoring: Improving the Design of Existing Code(中译名:<重构——改善既有代码的设计>)一书中与Kent ...

随机推荐

  1. MYSQL复习笔记7-索引

    Date: 20140207Auth: Jin 索引是根据表中一列或若干列按照一定顺序建立的列值与记录行之间的对应关系表. 索引的主要作用 快速存取数据 保证数据记录的唯一性 实现表与表之间的参照完整 ...

  2. uboot中的快捷菜单的制作说明

    转:http://blog.chinaunix.net/uid-22030783-id-366971.html 在uboot中加入快捷操作菜单的方法非常简单,在论坛发布的uboot201003V1.1 ...

  3. app生成工具

    国内主流的在线APP生成工具 应用公园:http://www.apppark.cn/ 追信魔盒:http://app.zhui.cn/ 安米网:http://www.appbyme.com/ 简网AP ...

  4. UNICODE串转换成char类型串的四种方法

    1. 调用 WideCharToMultiByte() API int WideCharToMultiByte (     UINT    CodePage,                //1 U ...

  5. iOS: 工具栏控件UIToolBar和工具栏按钮控件UIBarButtonItem的使用

    一.工具栏控件:UIToolBar:UIView 介绍: ToolBar工具栏是视图View的属性,可以在工具栏上添加工具栏按钮Bar Button Item(可以是自定义的Custom.也可以是系统 ...

  6. JS排序:localeCompare() 方法实现中文排序、sort方法实现数字英文混合排序

    定义:用本地特定的顺序来比较两个字符串. 语法:stringObject.localeCompare(target) 参数:target——要以本地特定的顺序与 stringObject 进行比较的字 ...

  7. GDALDataset的创建和销毁

    之前在GDALDestroyDriverManager 分析中没有看到对dGDALDatasert的回收.先看一个例子程序,这个例子打开了一个tif文件,读取了一些基本信息. 为了简单示范,没有写成C ...

  8. EffectiveJava(26)使用泛型类替代普通类

    使用泛型编写类比使用需要在客户端代码中进行转换的类型更加安全,并且对去其他程序员来说更加容易扩展,我们应该将可以用泛型代替的非泛型类优化 那么,如何将类泛型化呢? 这很简单.首先,给他的声明添加一个或 ...

  9. android带有文字的图片按钮的两种实现方式

    android带有文字的图片按钮的两种实现方式 1). TextView对Button用相对布局,这要要求按钮的背景图片要留下空白位置给文字.这种方式开发比较简单,适合做一些风格一致的Button. ...

  10. 我如何向HRMM介绍MICROSERVICE

    一天我司招才猫姐(HR 大人)问我,你给我解释一下 Microservice 是什么吧.故成此文.一切都是从一个创业公司开始的. 第一章:从集中到分权 最近的创业潮非常火爆,我禁不住诱惑也掺和了进去, ...