深入讨论通用权限组件的理论和设计实现。

本人最近正在为公司的多个项目(包括未来项目)做通用的权限组件,在本论坛上看到”dunel”大侠的一个帖子 http://www.jdon.com/jivejdon/thread/13450.html,然后才注册并发表此 话题,欢迎大家耐心阅读并指正。

目前已经发布了一个版本并供几个项目使用,先简单介绍一下组件的情况:

1.模式:建立在RBAC理论技术上的权限模式

2.技术:是以Java编写的一个组件(计划在下一个版本做成一个框架)

3.结构:包括两部分:
(A)权限配置管理平台,一个Web应用(即一个war包),用于注册受控资源,管理角色,和授权(把角色指派给宿主系统的用户),本平台是可选的
(B)权限服务组件,一个嵌入式组件(即一个jar包),提供访问控制服务和权限配置服务(后者供宿主系统通过接口调用实现权限配置管理,可以代替权限配置管理平台)

4.实现机制:权限相关数据与宿主系统的数据逻辑上式独立的,宿主系统通过嵌入权限组件,本地调用组件提供的相关服务接口实现权限配置管理和访问控制,组件提供四种服务:
(A)授权服务:用户访问宿主系统的受控资源时,宿主系统把用户ID和被访问资源ID传递到授权服务接口,授权服务接口返回是否可以访问的结果信息,宿主系统可以一次加载用户的所有权限信息,也可以在每次用户访问时才调用授权服务接口。
(B)实体管理服务:提供受控资源(实体)的增、删、改、查等管理服务。
(C)角色管理服务:提供角色的增、删改、查管理服务和为角色配置受控资源的服务
(D)授权管理服务:提供为宿主系统用户指派、移除角色的服务。
宿主系统可以把UI相关的实体名以URI来注册,权限组件提供默认的Filter进行拦截,对API的实体名以API对应的方法名的全限定名进行注册,权限组件提供默认的Interceptor以AOP的方式进行拦截,这样,宿主系统就不需要在业务层和页面层编写与权限控制相关的代码,权限这个功能编程了一个可以切入和移除的Aspect。

5.功能范围:目前只能控制功能权限,数据权限控制还没实现。

在本论坛看到了各位高手的一些关于权限模型和实现的讨论,觉得受益匪浅,所以本人也想针对权限控制提出一些本人的观点和针对一些困难请求解决方案,我的帖子将围绕以下几个方面讨论:

RBAC模型和相关概念
功能权限和数据权限
权限、角色与组织机构、用户之间的关系

1. RBAC模型和相关概念(以下观点是本人在理解RBAC模型之后结合个人意见的观点,如不合理,请指出并欢迎讨论)

1.1 术语定义:

受控资源:系统需要进行访问控制的资源,包括功能性资源和数据性资源,所以受控资源分为功能实体和业务实体:
(A)功能实体:从抽象角度来看,用户(不一定是人,也可以系统)使用系统只有两种途径:通过UI访问,如:按钮、页面、菜单等;通过API访问,如服务接口,DAO接口。经过这样的定义,对功能实体的操作就可以抽象成只有一种:访问。
(B)业务实体:即宿主业务系统相关的领域模型对象,如房地产交易系统的客户、楼盘、房间、合同等。对于业务实体,其操作可以抽象成四种:增加、删除、修改、读取。

操作行为:对受控资源的操作类型的抽象,对功能实体,操作行为只有“访问”,对业务实体,操作行为有“增加”、“删除”、“修改”、“读取”

权限:权限是实体+操作的组合,即“对‘什么资源’执行‘什么操作’”,因此,每个功能实体只能有一种权限,但每个业务实体,可以有最多四种权限。

角色:角色抽象上跟权限是同一概念,因为角色是反映用户可执行的权限,角色实际上是权限集,因为“人”会频繁变动,但“角色”却很少变动,所以才需要引入“角色”这个概念。

1.2 关系概念

实体之间的关系:实体与实体之间可以表现为从属关系和关联关系。
(A)从属关系,实体可以拥有一个父结点,多个子结点,拥有子结点权限的前提是必须拥有父结点权限,例如“楼盘信息”页面,拥有“查询楼盘”、“修改楼盘”两个按钮,那么“楼盘信息”页面这个功能实体就是“查询楼盘”和“修改楼盘”两个按钮功能实体的父结点,用户只有在拥有“楼盘信息”页面的访问权的前提下,才可能拥有“查询楼盘”和“修改楼盘”两个按钮的操作权。
(B)关联关系:实体之间的松散耦合关系,如A页面内嵌了C页面,B页面也内嵌了C页面,C既不属于A,也不属于B,这种情况,A与C、B与C之间就构成了关联关系。

角色与实体之间的关系:角色与实体之间存在多对多关系

角色之间的关系:扩展关系与排斥关系,建立这些关系主要是方便管理,对于正向授权,可以使用扩展关系,如角色A拥有1、2、3的权限,角色B比角色A多拥有4的权限,则角色B可以扩展角色A,然后为它指派4;对于反向授权,可以使用排斥关系,例子跟前者相反。对于这种关系还可以进一步扩展,就是一个角色可以扩展自多个角色,也可以排斥多个角色。根据实际情况,扩展关系比较常用。

1.3 存在争议

【讨论点1】其实对“业务实体”的操作最终都会表现为一种功能,如:对“合同”执行“修改”操作,可以被定位为“修改合同服务”这样一个功能,以业务接口的方式暴露出来,因为一般的业务系统设计中,业务系统并不会把纯数据的操作(即DAO)直接暴露给外界使用,而是把业务接口暴露给用户使用,用户只能通过业务接口对数据进行操作,不能直接操作一个业务对象。理论上,一个业务操作可能对应多于一个的业务实体的多于一个的操作,举个例子,删除一个可售楼盘信息这个业务,包括了多个业务实体操作:可售楼盘+删除、楼盘的房间+删除、销售信息+修改。所以,从更高一层的抽象看待“受控资源”,它可以全部被定义为功能实体,而对受控资源的“操作”,则都可以被抽象成“访问”。

【讨论点2】基于RBAC的理解模型,还应不应该允许直接把权限分配给用户,从本人的角度来看,由于权限对于大部分系统都是一个Aspect的问题,因此权限这个Aspect是不应该包括用户的,即权限模块的数据模型只有实体、角色及其之间的关系,用户作为另外一个Aspect(可以做成一个统一用户管理模块),如果只允许把角色与用户建立关系,不允许用户之间指派权限,则从系统角度来看,“权限控制”与“用户管理”作为业务系统的两个Aspect模块,他们之间的联系就会更加简单和清晰,就是“权限.角色”-“用户”。但另外一个问题是,很多时候,管理人员需要为某些特定的用户在他拥有的角色上根据实际需要分配多若干个权限,如果都需求定义角色,就会出现角色泛滥,不便管理了。这是从系统设计角度与现实情况角度相矛盾的地方。

2.功能权限和数据权限

2.1 概念定义
功能权限:在第1点已经阐述过,用户与业务系统进行交流,一般是面向服务的,即业务系统会把服务抽象成一个个功能点暴露给用户,功能权限实际上就是决定用户能否使用系统提供的功能点的问题,即“‘谁’对‘什么资源’进行‘什么操作’”(而根据上面的第1点的讨论点1,权限可以被简化为对功能实体的访问操作,即“‘谁’访问‘什么功能实体’”)。

数据权限:关于这个概念,有多种说法,有人认为对一个对象进行不同的操作就表现为数据权限,比如对“论坛帖子”进行“阅读”和“修改”、“删除”等属于数据权限,但本人认为(结合第1点的讨论点1),这归根结底还是功能权限(或者说,可以被定义为功能权限)。本人理解的数据权限,是指基于特定用户的权限控制,即“‘谁’访问‘什么资源’当中的‘哪些资源’”的问题,举个例子:分论坛A的版主与分论坛B的版主拥有同样的角色“版主”,即他们的功能权限是一致的,但A版主只能管理A论坛的帖子,B版主只能管理B论坛的帖子,这时,RBAC就不能解决这类权限问题,这种情况,角色就需要与组织结构有所联系了。进一步,更复杂的情况:高级经理能审批50万以上的合同,中级经理只能审批50万以下的合同,这就更加需要引入“规则”进行权限控制了。

2.2 权限组件是否(能否)把数据权限控制也纳入它的功能范围

本人对这点非常困惑,但经过各种权衡,本人设计的权限组件还是“暂时”不把数据权限纳入通用权限组件的范畴,理据如下:

(A)功能需求上的考虑:“权限”是一个很大的概念,也和模糊,功能性权限无可非议,是纯权限的功能,但对于如上述2.1所述两个例子,就存在角度问题,从权限功能角度看,它们属于权限的功能需求,但从业务的角度看,很明显,上述两个例子都属于业务规则,他们的权限会根据业务的变化而变化的,例如论坛的分版主原来只可以管理本版的数据,但需求改变了,他也可以管理其他版的数据;对于第二个例子,变化更加难于控制,可能需要上要求高级经理可以审批的金额数变化了,可能因为经理的级别变化了,甚至可能会加入更多的规则。这两个例子,后者更加偏向于业务规则,本人觉得这种于业务规则紧密集合的“权限”,不应归纳到“权限组件”去实现,但对于第一个例子,可以通过引入组织机构得到一定程度的解决,但这样也引出了一个新的问题:权限于组织机构的关系,对于业务系统来说,两者应该是两个独立的Aspect,还是应该整合在一起呢?这个问题在第3点进行讨论。

(B)系统设计上的考虑:系统设计的原则是功能独立单一,结构清晰,依赖耦合低,灵活和可扩展的。因此,我们目前主要的业务系统架构是:展示层-业务层-数据层,把所有业务逻辑集中在“业务层”统一管理,这样的好处有:
功能单一:各层负责各层的功能,只要是面向接口通讯,每一层的修改都是独立的,而且因为功能独立,也便于维护;
业务封装:所有业务被封装在业务层,使业务可以被灵活的组合和重用,业务与展示也分离了;
安全稳定:所有业务处理被封装到业务层中,无论外界传递一些什么破坏性数据过来,业务层都只做它该做的事,不会做它不该做的事情,例如用户用户系统的“修改用户基本信息”服务,但他尝试把密码也修改传递过来,而“修改用户基本信息”这一服务把所有业务逻辑封装了,它不会受外界影响,接收到用户信息对象时,即使密码被改变了,由于它的业务逻辑不处理密码,密码也不会被修改被持久化到数据库。
数据层独立:数据持久化动作交给数据层(通常是DAO)处理,DAO不管业务,把所有数据的访问都抽象为“增”、“删”、“改”、“查”,DAO可以被所有业务模块公用,也可以进行更换,例如因为性能或成本需要更换持久层ORM框架、更好数据库(更准确来说是数据源)。
而“权限”,这作为一个“横切面”的Aspect,根据AOP设计理论,是应该从系统的三层结构中分离开来的,三层架构是系统的一个“维度”,权限又是另外一个“维度”,彼此之间只有连接点(JoinPoint),没有耦合,彼此不可见。从这个角度来看,如果把与业务逻辑相关的所谓“权限”交给权限组件去做,则一来业务系统对权限组件依赖变成“硬性依赖”,二来业务逻辑被分散管理了。作为系统的设计人员,你会希望你的开发人员在修改业务逻辑的时候,需要从业务层和权限Aspect把零散的业务逻辑收集并理解吗?一旦将来系统的权限控制需求发生改变,需要更换权限组件,或者需要以硬件的方式来进行访问控制,你是不得不向上级领导申请人月资源去重新编写你的业务逻辑了吧?

(C)重技术实现角度考虑,如果需要把这类与业务规则有关系的数据权限控制交给权限组件实现,那么权限组件就需要设计成一个框架,提供标准的接口供业务系统根据不同的业务规则实现不同的访问控制策略,但需要抽象的定义一套能适应各种业务规则的接口(及其传递的参数,返回的结果),并不是一件十分容易的事情(当然,并不是不可能)。

(未完待续。。。)

[该贴被johnnylzb于2008-02-03 16:14修改过]

非常清晰的思路,与我思路基本一致,也指出了一些待讨论的地方,比较客观。JiveJdon3的权限思路也是基本按照这种思路设计的。

>权限组件是否(能否)把数据权限控制也纳入它的功能范围
我个人也认为数据权限属于业务性质范围,所以,不应有纳入通用的权限组件,所以在JiveJdon3中,专门做一个ForumMessageShell来对数据权限进行实现,而且随着业务变化,可能涉及修改面比较多,因此使用Proxy代理模式。

这里就体现模式的作用:不能用框架(通用权限组件)实现的,在微观上我们有模式来,总体目标就是将权限尽量和业务功能分离,框架能够实现最大限度分离,框架无法发挥作用的,对于数据权限这样又属于业务,又区别其他特定业务功能的,就使用模式对付它。所以,模式和框架是设计人员最常用的两个武器(需要进阶的程序员必须学好这两个常用武器)。

权限这个问题在本站自开站以来一直在讨论,复杂主要在分析和设计两个方面,分析方面我们需要理解RBAC;在设计实现上,我们需要AOP框架和模式,所以,权限问题的解决能够考验一个程序员的全面素质。

所幸的是,在2007年即将过去,2008年春节来临之际,终于看到有人完整地分析设计了权限问题,可贺啊。

谢谢你的回复,很久就知道J道,以前水平太低,对于你的文章我读不太懂,现在参与Java开发两年了,有点心得,才敢在这里发言,其实我还有很多其他方面(设计方面,编码方面)的心得,希望以后多交流

>我个人也认为数据权限属于业务性质范围,所以,不应有纳入通用的权限组件,所以在JiveJdon3中,专门做一个ForumMessageShell来对数据权限进行实现,而且随着业务变化,可能涉及修改面比较多,因此使用Proxy代理模式。

请问这句话如何理解呢?如何使用Proxy模式呢?还有,针对Proxy,我有一点迷惑,由于我的设计是权限组件和用户管理组件都是宿主系统Aspect方面的问题,而我的权限组件是依赖于用户组件的(通过向用户组件传递宿主系统标识,获取该宿主系统的用户列表,用于把用户与角色进行绑定,即授权),在我设计权限组件的时候,领导还没有详细考虑统一用户管理组件的问题,于是一个同事就用很短时间写了一个提供用户CRUD的组件,我当时在想,这个组件是不稳定的,不能直接使用,于是我就为权限组件写多了一个模块(com.***.***.proxy.***),这个Proxy的作用是为权限组件提供足够的用于与用户组件打交道的服务接口(权限组件并不需要增、删、改,只需要查),并把用户组件返回的用户DO转换成权限组件自定义的用户DO,这样做的目的是,用户组件不稳定,将来肯定会有变化(说不定由本人负责设计),为了屏蔽这些不稳定因素,避免因为用户组件重新设计而影响权限组件,所以设计了一个Proxy来做“中介”,将来用户组件变化,只需要集中修改Proxy就行。我的这个问题可能是一个“文字问题”,究竟我使用的这种模式,是代理模式,还是适配器模式呢?

另外,我的讨论话题还没完,其实我还想讨论:究竟“权限”与“组织架构”是否应该设计在一起,还是应该分开,以及角色、用户、组织机构之间的关系问题,不过我暂时还没有想清楚如何条理的表达我的看法,所以还没写出来而已。

我讲proxy主要是指数据权限方面,因为数据其实就是业务对象,围绕业务对象有其特定的业务操作,比如订单操作,那么对于当前这个订单是只能被创建者操作这样的权限,就依靠权限代理来做。

代理模式和装饰器模式有一些区别,可在本站查询到,实际应用中我们不必太在意这些区别。

-->讨论点1:你所说的类似于功能与功能组,事实上,我觉得没有这个问题,真的有必要对原子操作进行受保吗?相对于业务系统,原子操作只是业务应用(business service)的组成部分,因此受保的对象应该是业务应用而不是原子操作,就用你所举的例子讲,删除一个可售楼盘信息就是一个业务应用,也是用户应该关注的,至于里面包括多少原子操作,甘本不需理会。
至于>>把纯数据的操作(即DAO)直接暴露给外界使用
有service之后不应该有这种情况。

-->讨论点2:应不应该直接为用户分配权限,这个应该是需求上的问题,呵呵(客户就是上帝),他有这种要求,你就不能不干。
话说回来,一个优秀的组件就应该考虑尽可能多的情况,而你要做通用的组件更甚之,不能因为你某些设计思想或理念而降低它的应用价值。

这是我第二次来Jdon阅读了 各位关于权限系统的讨论,
我进公司不久,就被安排负责公司权限、组织机构组件的维护和开发。因为公司所有的OA项目都使用这个平台,在日常的维护中发现了许多问题,其中很多对资源的控制没有通过权限系统控制,特定资源的控制代码泛滥、难以维护的问题。
许多同事在其中花费了大量的时间,需求一变就得反馈回来改代码。

这里很多都是 因为 数据权限 的处理不当,起因也是公司自己的权限组件对数据权限没有提供很好的支持。

数据权限是否应该纳入权限组件? 我个人也赞成楼主以及banq的意见。
其中banq提到了 使用代理模式来处理数据权限,楼主也对此提出了自己的观点和疑问。但我还是有点不太清楚,使用代理模式来处理数据权限具体原因,希望各位能详细解说一下,谢谢!

[该贴被goosped于2008-03-27 13:55修改过]

在web程序中,个人还是觉得规划好URL,然后通过对URL的控制来实现权限。感觉会比AOP来得好!

纵观我们系统实现的功能,一部分是简单的CRUD,这些通常是由业务层调用DAO层对应的方法;一部分是由业务层协调多个DAO层对象一起工作的复杂业务功能。这些功能在实现的时候都会按照实际业务处理情况组织在某个模块(在界面上表现为窗体或者网页)里面。那么按照RBAC的理论,这些模块就是一个个的资源,而针对这些资源存在有一系列的可以进行的操作,如“访问”、“增加xx”,“修改xxx”,"删除xxx",“读取xxx”。操作,这些操作通常会与界面挂钩,如“访问”用来表示对窗体的可见性,“增加xxx”用来表示对按钮的可见性。按照我们一般的处理方法,用户菜单的构建就通过对模块的“访问”权限来组织。这里的模块都对应到具体的业务逻辑类。

针对johnnylzb讲的受控资源,我觉得这样来划分可能更加贴切、直观一些。

好文章,解决了很多问题,同样的一些顽固疑惑难以攻克。
说点个人的浅见。


受控资源:
(A)功能实体
这个很容易理解,就是接口,access关系
(B)业务实体
这个同意其它人所说,他不会单独出现,应当和接口一同出现。可以规划到A

实体之间的关系
(A)从属关系
赞成。并认为是多继承关系,也就是子节点可以拥有多个父节点
(B)关联关系:
现在想不出具体的作用。

角色和角色之间的关系
这些都没啥问题。

1.3 存在争议
【讨论点1】其实对“业务实体”的操作最终都会表现为一种功能
支持这种观点

【讨论点2】基于RBAC的理解模型,还应不应该允许直接把权限分配给用户,
这个多说几句废话。两种观点
第一种,用户本身就是一种角色,该角色就是他自己。从逻辑模型上来看,角色(更抽象的概念)是基类,Party和Party可扮演的角色是其子类。人和组织是Party的子类。这样的话,权限直接赋给人也无可厚非。
第二种,用户,角色是完全两种概念,该情况下支持权限只分配到角色,用户必须扮演角色以获得权限。角色泛滥可以通过更细粒度的角色设置解决。举个例子,所有用户可卖产品P1,P2,用户X可多卖一种特殊产品P3。那么可把卖产品P1,P2定义为一种角色R1,卖产品P3定义为一种角色R2,用户X拥有R1,R2两种角色。该类变化情况一般都是X将R2的权限转交给Y,那么角色上不需要重新定义。
本人更倾向与第二种解决方式。

2.功能权限和数据权限
这是tmd最烦人的地方(请原谅用词,这只是一种情绪的发泄)
按本人的理解,只有两种权限,接口权限和接口内权限,或者说,粒度的问题。
权限可以理解为Who对What进行How的操作Where条件。。。
Who - 很简单,角色
What - 资源,即接口
How - 这就有点问题了,一般可以从接口的参数上看到,但已经有些不受控制了。
Where - 这完全就是tmd接口内部的问题了。
我们当然可以认为权限到What就为止了,剩下的都是业务逻辑。这样做出一个权限系统绝对是通用的。问题是用户,程序员,架构师会接受吗?
还是举刚才那个例子,卖产品是一个接口,卖什么具体的产品决不会做成接口,那样接口就泛滥了。
销售员一般都定义为可以卖什么样的产品。这显然不是硬编码,应该做成一种类似权限样的配置。按照刚才的想法,我们需要在两个地方做配置,一是通用系统上配置销售权限,二是到业务系统上配置具体的产品权限。业务系统上还是要做一遍类似权限的东西,另外从管理上,我们只能从行政角度保持两系统的数据一致。
再举一个例子,就举RBAC自身权限管理本身。显然会存在分级的权限分配。如本部门的权限管理员可对本部门的人员分配本部门的权限。显然我们不会为每个部门做一个权限分配接口。又是一个接口内的逻辑问题。你如何认为,这是一个应当如何解决的问题。
一种想法是这些统统都是Contraint的东西,Contraint是RBAC的一部分,那么Contraint如何设计?
Contraint仅设计成一个接口,具体的实现由业务系统完成,就像JDBC一样。那么这个耦合性你怎么看?还有刚才那个问题,Contraint接口如何设计?

代码已经共享了吗?
是否可分享代码,大家学习一下?

对楼主和accipiter 对权限的理解感动。
我也是一直做权限管理的,从04年开始研究这个领域,尤其是数据级权限领域。

楼主的分析非常到位,也指出了功能权限与数据权限的区别,在那个层次分离功能权限与数据权限。对于楼主非常好的地方,我也就不多废话表扬了。对于有些歧义的地方,提出来与各位讨论。

1,“实体管理服务”这部分,我觉得属于业务范畴,业务系统肯定实现实体管理,并要加入很业务的“流程”与事务管理。如果在权限系统再次维护一遍,显然工作量太大。权限系统,可以采用读取业务系统的实体模式。也要有对实体模型进行“规则”描述的能力。

2,“权限是实体+操作的组合”。确实是这样的,是实体与操作的组合。从技术层面来说就是CRUD操作四种,从业务层面来说可不是这样的。比如打印、审批。 所以,我建议:将操作提取出来,做为“功能”。为“审批”功能赋予权限,为“打印”功能赋予权限。

3,“实体与实体之间可以表现为从属关系和关联关系”。楼主理解也没有错。不过我认为,如果这样考虑。那么这个系统必然过于庞大、复杂。 关联应该属于业务层次概念,进行权限判断后,业务开始事务进行关联操作。 从属关系属于权限策略,不属于权限模型。“策略”与“模型”还是有些差别的,我不大会表达出来意思。请各位意会一下。

那么,权限就分成功能与数据级。数据级,也是按照“功能点”或者叫“权限点”,设置权限。
弱化CURD概念,突出业务概念,这样有助于交给业务管理人员维护。
弱化实体维护,弱化实体直接关系业务属性,重点在于业务实体规则描述。

对于,系统开发方面,我与很多开发者沟通,总结出这样的指导思想:
1,功能权限使用拦截器方式就可以了;
2,数据权限,最好将其抽取出来集中管理,不要与业务耦合在一起,也不要分散在系统各个地方。即便使用硬编码方式实现数据权限,也要提取出来。比如查询订单: 要抽取这样的解开:public Collection selectBills( User user );
3,需要考虑组员知识层次结构,对于使用规则引擎等高级知识,需要做出相关技术评估。否则,后期维护起来也很耗费成本。

具体接口方式,建议参考:http://www.metadmin.com/zh/docs/javadoc/org/back/MetadminService.html (注:这是商业产品,大家可以参考思想)。 接口就8个方法,应该够用了。

2008-02-05 11:04 "@banq"的内容
我讲proxy主要是指数据权限方面,因为数据其实就是业务对象,围绕业务对象有其特定的业务操作,比如订单操作,那么对于当前这个订单是只能被创建者操作这样的权限,就依靠权限代理来做。 ...

刚了解了一下规格模式,发现用规格模式来做挺好的,不管是数据过滤还是单个的业务对象操作都可以。只是没实践过,banq觉得呢?