通用权限管理设计篇(二)

cqm

贡献于2011-05-20

字数:7687 关键词: 权限设计

权限系统(1)--基本模式 在系统中发生的事情,抽象的说都是某个主体(subject)在某个资源(resource)上执行了某个操作(operation)。 subject --[operation]--> resource 所谓权限管理,就是在这条信息传递路径中加上一些限制性控制。 主体试图去做的 limited by 系统允许主体去做的 = 主体实际做的。 可以看到,权限控制基本对应于filter模式。subject试图去做的事情应该由业务逻辑决定,因而应该编码在业务系统中。 先考虑最粗粒度的控制策略,控制点加在subject处,即无论从事何种操作,针对何种资源,我们首先需要确认subject是受控的。只有通过认证的用户才能使用系统功能,这就是authentication。boolean isAllowed subject) 稍微复杂一些,控制可以施加在subject和operation的边界处(此时并不知道具体进行何种操作),称为模块访问控制,即只有某些用户才能访问特定模块。isAllowed(subject, operation set) 第三级控制直接施加在operation上,即操作访问控制。operation知道resource和subject(但它尚没有关于resource的细节知识),我们能够采取的权限机制是bool isAllowed(subject, operation, resource), 返回true允许操作,返回false则不允许操作。 最简单的情况下,subject与resource之间的访问控制关系是静态的,可以直接写成一个权限控制矩阵 for operationA: resourceA resourceB subjectA 1 0 subjectB 0 1 isAllowed(subjectA, resourceA)恒等于true 如果多个operation的权限控制都可以通过这种方式来表示,则多个权限控制矩阵可以叠加在一起 for operationA, operationB: resourceA resourceB subjectA 10 01 subjectB 01 11 当subject和resource的种类很多时,权限控制矩阵急剧膨胀,它的条目数是N*M。很显然,我们需要进行矩阵分解。这也是最基本的控制手段之一: 在系统中增加一个瓶颈,或者说寻找到隐含的结构。 subject_resource = subject_role * role_resource 这样系统权限配置条目的数量为 N*R + R*M, 如果R的数目远小于subject和resource,则实现简化。这称为RBAC(role based access control),它的一个额外好处是权限系统的部分描述可以独立于subject存在,即在系统中没有任何用户的时候,通过角色仍然可以表达部分权限信息。可以说角色是subject在权限系统中的代理(分解)。 有时候引入一个瓶颈还不过瘾,有人引入组的概念,与role串联, subject_resource = subject_group_role * role_resource 或着group与role并联, subject_resource = subject_group * group_resource 与role稍有不同,一般情况下group的业务含义更加明显,可能对应于组织结构等。将组织机构明确引入权限体系,有的时候比较方便,但对于权限系统自身的稳定性而言,未见得有什么太大的好处。并联模式有些多余,串联模式又过于复杂,细节调整困难,特别是多条控制路径造成的冲突情况。一般情况下,我不提倡将group引入权限控制中。 比操作控制更加深入的控制就是数据控制了,此时需要对于resource的比较全面的知识。虽然表面上,仍然是 boolean isAllowed(subject, operation, resource),但控制函数需要知道resource的细节。例如行级控制(row-level)或者列级控制(column-level)的实现。因为我们一般情况下不可能将每一个条目都建模为独立的resource,而只能是存在一个整体描述,例如所有密级为绝密的文档。在witrix平台中,数据控制主要通过数据源的filter来实现,因为查询条件(数据的定位条件)已经被对象化为Query类,所以我们可以在合适的地方自由的追加权限控制条件。 以上的讨论中,权限控制都是根据某些静态描述信息来进行的,但现实世界是多变的。最简单的,当subject从事不同业务时,对应于同一组资源,也可能对应的权限控制并不同(在witrix平台中,对应于IDataSource的模式切换)。更复杂一些, 在不同的时刻, 我们需要根据其他附加信息来作出是否允许操作的判断, 即此时我们权限设置的不仅仅是一些静态的描述信息, 而是一个完整的控制函数, 这就是所谓的工作流权限控制,一种动态权限控制. 权限系统(2)—operation 权限控制可以看作一个filter模式的应用, 这也符合AOP思想的应用条件。在一个简化的图象中,我们只需要将一个判别函数 isAllowed(subject, operation, resource)插入到所有安全敏感的函数调用之前就可以了。虽然概念上很完美,具体实现的时候仍然有一些细节上的问题。基本的困难在于很难在最细的粒度上指定权限控制规则(连续的?动态的?可扩展的?),因而我们只能在一些关键处指定权限规则,或者设置一些整体性的权限策略,然后通过特定的推理来推导出细粒度的权限规则,这就引出结构的问题。我们需要能够对权限控制策略进行有效的描述(控制策略的结构),并且决定如何与程序结构相结合。subject, operation和resource为了支持推理,都可能需要分化出复杂的结构,而不再是简单的原子性的概念。而在与程序结构结合这一方面,虽然AOP使得我们可以扩展任何函数,但这种扩展需要依赖于cutpoint处所能得到的信息,因而权限控制的有效实施也非常依赖于功能函数本身良好的设计。有的时候因为需要对结构有过于明确的假定,权限控制的实现不得不牺牲一定的通用性。 下面我们将分别讨论一下operation, subject和resource的结构分解的问题。首先是operation。 说到推理结构,让人最先想起的就是决策树,树形结构,在面向对象语言中可以对应于继承。金字塔式的树形结构也正是在现实世界中我们应用最多的控制结构。通过层层分解,operation的结构可以组织为一棵树, 应用程序 ==> 各个子系统 ==> 每个子系统的功能模块 ==> 子功能模块 ==> 每个模块的功能点(具有明确的业务含义) ==> 每个功能点对应的访问函数(程序实现中的结构) 一个常见的需求是根据权限配置决定系统菜单树的显示,一般控制用户只能看到自己有权操作的功能模块和功能按钮。这种需求的解决方法是非常直接的。首先,在后台建立子系统到功能模块,功能模块到功能点以及功能点到实现函数之间的映射表(如果程序组织具有严格规范,这甚至可以通过自动搜集得到)。然后,在权限配置时建立用户与功能点之间的关联。此时,通过一个视图,我们就可以搜集到用户对哪些功能模块具有访问权限的信息。 为了控制菜单树的显示,witrix平台中的SiteMap采用如下策略: 1. 如果用户对某个子功能具有操作权限,则所有父菜单项都缺省可用 2. 如果用户对某个功能具有操作权限,并且标记为cascade,则所有子菜单项都自动缺省可用 3. 如果明确指定功能不可用,则该菜单及子菜单都强制不可用 4. 如果明确指定功能对所有人可用,则不验证权限,所有子菜单自动缺省可用 4. 强制设定覆盖缺省值 5. 不可用的菜单缺省不可见 6. 明确标记为可见的菜单即使不可用也可见 7. 父菜单可见子菜单才可见 我们通过预计算来综合考虑这些相互影响的控制策略。尽量将推导运算预先完成也是解决性能问题的不二法门。 在witrix平台中,每一次网络访问的url都符合jsplet框架所要求的对象调用格式,需要指定objectName和objectEvent参数,这就对应于功能点的访问函数。访问控制点集中在objectManager并且访问格式是标准的。使用spring等AOP方式实现细粒度访问控制,困难似乎在于不容易引入外部配置信息(例如功能点信息等),而且控制点所对应的对象函数格式也不统一,因而多数需要在细粒度上一一指定。 权限系统(3)—subject 权限控制中,subject可能不会简单的对应于userId, 而是包含一系列的security token或certificate, 例如用户登陆地址,登陆时间等。一般情况下,这些信息在权限系统中的使用都是很直接的,不会造成什么问题。 subject域中最重要的结构是user和role的分离,可以在不存在user的情况下,为role指定权限。有人进一步定义了userGroup的概念,可以为userGroup指定role,而user从其所属的group继承role的设置。一般情况下,我不提倡在权限系统中引入userGroup的概念。这其中最重要的原因就是它会造成多条权限信息传递途径,从而产生一种路径依赖, 并可能出现信息冲突的情况。一般user与group的关联具有明确的业务含义,因而不能随意取消。如果我们希望对user拥有的权限进行细调,除去user从group继承的某个不应该拥有的权限,解决的方法很有可能是所谓的负权限,即某个权限条目描述的是不能做某某事。负权限会造成各个权限设置之间的互相影响,造成必须尝试所有权限规则才能作出判断的困境,引出对额外的消歧策略的需求,这些都极大的限制了系统的可扩展性。在允许负权限的环境中,管理员将无法直接断定某个权限设置的最终影响,他必须在头脑中完成所有的权限运算之后才能理解某用户最终拥有的实际权限,如果发现权限设置冲突,管理员可能需要多次尝试才能找到合适方案。这种配置时的推理需求可能会增加配置管理的难度,造成微妙的安全漏洞,而且负权限导致的全局关联也降低了权限系统的稳定性。我更倾向于将group作为权限设置时的一种辅助标记手段,系统中只记录用户最终拥有的角色,即相当于记录用户通过group拥有权限的推导完成的结果, 如果需要权限细调,我们直接在用户拥有的角色列表上直接进行。当然,如果实现的复杂一些,权限系统对外暴露的接口仍然可以模拟为能够指定userGroup的权限。 推理在面向对象语言中最明显的表现是继承,所以有些人将subject域中的推理直接等价于role之间的继承问题,这未必是最好的选择。继承可以形成非常复杂的推理关系,但是可能过于复杂了(特别是直接使用sql语句无法实现树形推理查询)。按照级列理论,从不相关发展到下一阶段是出现简单的序关系,即我们可以说subject出现级别上的差异,高级别subject将自动具有低级别的权限。一种选择是定义roleRank,规定高级别role自动具有低级别role的权限,但考虑到user与role的两分结构,我们也可以同时定义userRank和roleRank,规定高级别user自动具有低级别的role,而role之间不具有推理关系。在面向对象领域中,我们已经证实了完全采用继承来组织对象关系会导致系统的不稳定,所以我倾向于第二种选择,即将role看作某种类似于interface的东西,一种权限的切片。为了进一步限制这种推导关系,我们可以定义所谓的安全域的概念. security domain, 规定推导只能在一定的域中才能进行。 select user.userId, role.roleId from user, role where user.userRank > role.roleRank and user.domain = role.domain 将权限控制一般需要施加在最细的粒度上,这在复杂的系统中可能过于理想化了。复杂的情况下我们需要进行局部化设计,即进行某些敏感操作之前进行一系列复杂的权限校验工作。当完成这些工作之后,进入某个security zone, 在其中进行操作就不再需要校验了。 总的来说,权限系统采用非常复杂的结构效果未必理想。很多时候只是个管理模式的问题,应该尽量通过重新设计权限空间的结构来加以规避。不过在一些非常复杂的权限控制环境下,也许简单的描述信息确实很难有效的表达权限策略(虽然我从未遇到过),此时尝试一下规则引擎可能比在权限系统中强行塞入越来越多的约束要好的多 权限系统(4)—resource 权限管理中进行数据访问控制,其基本模式如下 operation target = selector(resource) selector = user selector + auth filter 这里需要对resource的结构,以及选择算子的显式建模。selector必须允许权限系统追加filter,例如 IDataSource包中所使用的Query对象。 sql语言的表达能力有限, 作为选择算子来使用有时需要resource作一些结构上的调整,增加一些冗余的字段。例如表达一段时间内的利率,我们需要使用from_date和to_date两个字段来进行描述,其中to_date的值与下一条记录的from_date相同。 value from_date to_date 0.01 2003-01-01 2003-05-01 0.012 2003-05-01 2004-01-01 如果表达一条航线中的多个阶段,我们可能会在每条记录中增加起始站和终点站两个字段。 更重要的一个常见需求是树形结构在关系数据库中的表达。为了能够直接操纵一个分支下的所有记录,在层次固定的情况下,我们可能会增加多个分类字段,例如数据仓库中的层次维度。在层次数目不确定的情况下,我们将不得不使用层次码或者类似于url的其他方案,通过layer_code like '01.01.%' 之类的语句实现分支选择。为了限制选择的深度,我们可能还需要layer_level字段。基于层次码和层次数,我们可以建立多种选择算子,例如包含所有直接子节点,包含自身及所有父节点等等。 http://www.dbazine.com/oracle/or-articles/tropashko4 权限系统(5)--动态性 动态权限最简单的一个表现是时限性,subject只在某个时间段内具有某种权限。这只需要在user和role的映射中,或者role自身的属性中增加startTime和expireTime即可。 更复杂的动态性一般与流程控制相关,此时权限控制应该由工作流系统完成,而不是在数据上增加越来越多的权限标记。在witrix平台中,使用tpl模板技术来定制权限设置。 通用用户权限系统设计 做了n多的MIS系统,很久以前就有这种想法,想把MIS系统中的用户权限 管理和审批流管理独立出来,做成单独的组件,但是因为各种各样的原 因,都没有去做,也许是太懒了。今天终于痛下决心,一定要把这两个 东西给做成组件,说干就干。因为代码还没有写完,今天暂时就把数据 库设计发上来,等代码搞好了,并且把代码搞的好看点后,我以后可能 会把这个权限管理组件和审批流管理组件开源。 今天暂时就看权限管理系统的数据库表设计吧。 子系统表,因为我这里设计的为了能够集成公司内部以后所有的系统的,所以建了子系统这张表,如果单个项目,这张表可以去掉。 模块表,系统中的各个模块。 模块功能表,模块中的各个功能(可以有两种功能,一种是页面的进入权限,一种是页面上的按钮事件) 用户表 角色表 用户分组表 用户部门表 用户权限表(也可以直接对用户赋予权限) 角色权限表(对角色赋予权限) 用户和分组关系表(一个用户可以属于多个分组) 用户和角色关系表(一个用户可以拥有多个角色) 用户分组和角色关系表(一个分组可以拥有若干角色) 通用用户权限系统设计(续) 非常抱歉,在前几天发的那篇《通用用户权限系统设计》中,文字描述 没有表达清楚我的意思,草草的贴出了数据库的表设计,请园子里的朋 友见谅。现在我针对一些朋友提的意见做一些回答,已经把上篇文章中 缺少的页面怎么控制的部分,补上去。 首先回答几个主要的问题: 1。一个人是否可以在多个部门? 我这里的设计是不能在多个部门的。虽然我想现实中一个人属于多个部 门的情况实存在的,但是在我们的系统里面可以把他放到一个部门里, 也不会造成问题,因为我这里的部门仅仅只是让人家看这个用户是属于 哪个部门的,跟权限没有关系。权限只跟角色和所属用户组有关系。 2。角色和用户组是否已经重复了? 刚开始我也这么觉得过,后来考虑了再三,并且在网上搜了一些资料, 还是留下来了。事实上用户组和角色区别比较小,我自己说不清楚,我 借用IT难民的这篇文章《用户、用户组、角色的区别和联系 》来做回答 ,可能更容易说明问题一点。 3。页面控制权限到底怎么做的? 这个别急好吗,我下面会详细解释。也有朋友问道会不会判断权限会不 会频繁读取数据库,我的回答是不会的,在用户登录的时候,我就把所 有权限放到session里面,用到的时候,直接判断,不会跟数据库打交道 。 4。关于数据的权限控制? 我原来做这个设计的时候,就没有考虑要设计数据权限控制,所以在我 这里设计里面这个需求是做不到的。可能都怪我“通用”这个词语吧, 其实我的本意是大部分MIS系统都能拿来用的功能权限控制。也有些朋友 提到,我的用户表字段肯定是不行的,还有把子系统表去掉等等。我想 我这个权限系统只有权限控制部分是可以公用的,用户表肯定每个系统 都不一样。 好了,回答了朋友们的问题。接下来,我相对完整的讲讲我这个权限控 制的思路吧,包括页面级怎么控制功能。 1。权限定义 其实也有朋友提到,权限定义和权限控制是需要分开的,其实我也是这 么想的,我们可以把权限定义做成一个专门的工具,最好不要集成在用 户系统里面。 我们一起来看看需要定义哪些信息。子系统编号,模块编号,功能编号 (用于控制树型菜单是否显示),功能名称,描述,页面名称(用于控制页面是否能够进入) ,按钮名称(用于控制功能按钮是否可用)(不好意思,上次弄的时候,把这个字段弄丢了; 2。给用户或者角色赋予权限; 3。在用户登录的时候,把用户所属角色,用户组,用户本身的权限都取出来,放到一起,存到session里面中去。 4。在我们的页面基类里面重写onload方法,在里面写上判断权限的代码。判断当前用户的session中是否有访问该页面的权限(没有权限则弹出提示,并且返回到请求页),以及该页面上的按钮(没有权限则置为不可用) 5。在导航树型菜单里面,把当前用户没有的菜单项去掉(其实就是功能编号) 6。这样的话,相当于把权限控制全部封装到基类里面了,业务模块不需要关心权限问题,只需要把做好的业务模块的权限注册到权限表中去就可以了(第1步)。 大概就这样子说吧,如果说的还明白,请朋友们跟贴。代码暂时还没有弄好,特别是页面,比较麻烦,最近时间也不是很多,请大家见谅。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ]
2 人已下载

下载文档

相关文档