续:关于权限系统的设计

02-12-07 jackyz
Note:

首先,向版主致歉。这原是关于权限系统设计问题的回帖,本不应该另开新贴。而在下的另开新贴,一方面是因为本人的观点中与很多人的观点差别较大,另一方面也担心其会被“埋没”在原贴的大量回帖中。此外,本贴的内容整理与编排耗费了我周末接近一整个晚上的时间,包含了我对近期项目中权限系统的思考与总结,希望引来大家足够的目光和指导。请版主体谅。

权限系统,其重要性当然不言自明。看见大家的方案,有相当多优秀的创意与经验,我的项目也有所涉及,以下说明的是我最近在一个企业 EJB 项目中初步实现的方案(以及设计考量),望与诸位共商榷。

前言:

权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“who 对 what(which) 进行 how 的操作”的逻辑表达式是否为真。

针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。

目标:

这个权限系统的设计,我主要考虑了这么几个目标:

直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要,系统不辞劳苦的实现了组的继承,除了功能的必须,更主要的就是因为它足够直观。

简单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。此外,同时具备 Role 和 Group 的系统难于理解,权衡之下,摒弃了 Role 概念。

扩展,我在以前的项目中也实现过基于 Role 概念的权限系统,但效果不太理想。之所以在这里摒弃 Role 的概念,另一个原因就是因为它不易扩展。通常 Role 的设计方式意味着预先已经定好了一组权限,这样的“预先设计”,常常会鼓励程序员 hardcode 这些权限相关的部分,而,如果这么做的话,当需要重新定义 Role 时,扩展就会变得极为困难。而采用可继承的 Group 概念在支持权限以组方式定义的同时有效避免了重定义时在扩展上的困难。

名词:

下面两个名词极其重要,是整个设计问题边界定义的关键,或许我的理解与通常的理解不同,在此有必要特别澄清。

粗粒度:表示类别级,即,仅考虑对象的类别,不考虑对象的某个特定实例。比方,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。

细粒度:表示实例级,即,需要考虑具体对象的实例,当然,细粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比方,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。

原则:

权限逻辑配合业务逻辑。即,权限系统以为业务逻辑提供服务为目标。纯粹纸面意义的极其复杂和精巧的权限系统,这里不作讨论。相当多细粒度的权限问题因其极其独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比方,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里我认为它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。

需要再次强调的是,这里表述的权限系统仅是一个“不完全”的权限系统,即,它不提供所有关于权限的问题的解决方法。它提供一个基础,并解决那些具有“共性”的(或者说粗粒度的)部分。在这个基础之上,根据“业务逻辑”的独特权限需求,编码实现剩余部分(或者说细粒度的)部分,才算完整。回到权限的问题公式,我的设计仅解决了 who + what + how 的问题,which 的权限问题留给业务逻辑解决。

概念:

User:用户。解决 who 的问题。

Group:组。权限分配的单位与载体。权限不考虑分配给特定的用户。组可以包括组(以实现权限的继承)。

Operate:操作。表明对 what 的 how 操作。

说明:

User

与大家的都一样,没什么好说的。

Group

与大家的类似,所不同的是,Group 要实现继承。即,在创建时必须要指定该 Group 的 Parent 是什么 Group 。在粗粒度控制上,可以认为,只要某用户直接或者间接的属于某个 Group 那么它就具备这个 Group 的所有操作许可。细粒度控制上,在业务逻辑的判断中,User 仅应关注其直接属于的 Group ,用来判断是否“同组”,间接的 Group 对权限的控制意义不大,试设想存在一个 All User 的 Group 是所有 Group 的祖先,这样的情形下,判断的结果不具备实际意义。

User 与 Group 是多对多的关系。即,一个 User 可以属于多个 Group 之中,一个 Group 可以包括多个 User 。

子 Group 与 父 Group 是多对一的关系。即,一个子 Group 只能有一个父 Group ,一个父 Group 可以包括多个子 Group 。

Operate

某种意义上类似于大家的 Resource + Privilege 概念,但,这里的 Resource 仅包括 Resource Type 不表示 Resource Instance。Operate 概念上与大家的观点区别比较,后面有详细的解释。

Group 与 Operate 是多对多的关系。

各概念的关系图示如下:

 User
  |*
  |
  |*   1
 Group---+
  |* |*  |
  |  +---+
  |*
 Operate
<p>

解释:

Operate 的定义包括了 Resource Type 和 Method 概念。即, what 和 how 的概念。之所以将 what 和 how 绑定在一起作为一个 Operate 概念而不是分开建模再建立关联,这是因为很多的 how 对于某 what 才有意义。比方,发布操作对新闻对象才有意义,对用户对象则没有意义。

how 本身的意义也有所不同,这里并非仅定义类 UNIX 的 RWX 三种操作,这样的定义对于文件系统是合理的,但,对于其他的应用领域或许就不是那么足够了。具体来说,对于每一个 what 可以定义 N 种操作。比方,对于合同这类对象,可以定义创建操作、提交操作、检查冲突操作等。可以认为,how 概念对应于每一个商业方法。

其中,与具体用户身份相关的操作既可以定义在操作的业务逻辑之中,也可以定义在操作级别。比方,创建者的浏览视图与普通用户的浏览视图要求内容不同。你既可以在外部定义两个操作方法,也可以在一个操作方法的内部根据具体逻辑进行处理。具体应用哪一种方式应依据实际情况进行处理。

这样的架构,应能在易于理解和管理的情况下,满足绝大部分粗粒度权限控制的功能需要。但是,除了粗粒度权限,无可否认,系统中必然还会包括无数对具体 Instance 的细粒度权限。这些问题,被留给业务逻辑来解决,这样的考虑基于以下两点。

一方面,细粒度的权限判断必须要在资源上建模权限分配的支持信息才可能得以实现。比方,如果要求创建者和普通用户看到不同的信息内容,那么,资源本身应该有其创建者的信息。如同 Unix 的每一个文件(资源),都定义了对 Owner, Group, All 的不同操作属性。

另一方面,细粒度的权限常常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。相比之下,粗粒度的权限更具通用性,将其实现为一个架构,更有重用价值;而将细粒度的权限判断实现为一个架构级别的东西就显得繁琐,而且不是那么的有必要,用定制的代码来实现就更简洁,更灵活。

结语:

希望我的文字表述清楚,也希望我的方案能激起大家讨论研究的兴趣。

欢迎讨论。

    

2
banq
2002-12-07 12:58
非常有分量的文章,文章从架构的高度将权限系统划分两大部分我认为是正确的。

但是舍弃role概念是否需要再进一步斟酌?

我个人认为,role可以是粗粒度和细粒度(业务逻辑)的接口,假设我们开发了一个基于粗粒度控制的权限框架软件,那么我们对外的接口是什么?我觉得应该是role, 具体业务实现可以直接继承或拓展丰富role的内容,后者属于细粒度。

role不是如同user或group的具体实体,他应该是接口概念,抽象的通称。

jackyz
2002-12-09 12:41
我的方法中与大家区别最大的部分可能就在于――在粗粒度的控制上“以 group 的继承取代 role 的组合”。

to: Banq,你说得有道理,但是请看我下面对于 cc 举例的分析。

to: cc,谢谢你的举例,非常直观。

我注意到你的举例与我以前实现的权限极其类似,它的关联是以一个“三元组合”的形式出现的 user-group-role ,即“谁在什么组充当什么角色”,也就是说,它实际上是以纯粹的组合方式定义权限的。基于组合的设计, Group 可以直接映射组织结构,Role 可以直接映射组织结构中的业务角色,比较直观,而且也足够灵活。

在这样的结构下,可以认为, Role 是一个非关键概念。试想,如果没有 role ,直接采用 user-group-permission 的关联也是可以实现组合的,只是对于一个用户来说,数据量可能会非常庞大。而 role 的引入实际上大大缓解了这种状况,也就是说一条记录意味着“给这个 group 的这个 user role 分配了定义的所有这些 permission ”,而不是每个 permission 一条记录,在这里,它仅仅意味着“一组权限”,它对系统的贡献实质上就是提供了一个比较粗颗粒的分配单位。在组合的方式下,这样的设计是合理的。

但是这一纯粹的组合方式存在这样的问题:

1. user-group-role 是一个扁平的关联关系,必须同时定义 user-group-role ,这个三元关系需要(而且必要)解构。比如,如何反应 user-group 之间的关系?ok,你可以定义 user-group 关系,那么,此时, user-group 与 user-group-role 两个关系之间是什么关系?ok,你还可以取消 user-group-role 再定义 group-role ,此时通过 group 作为中转就可以实现 user-group-role 了。问题是,在这样的定义下,role 的贡献――“定义一组 permission ”不再那么有意义,那么,为什么不去掉 role ,直接定义 group-permission 关系呢?

2. 注意,此时,user group permission 已经极其类似于我的 user group operate 方案了。

3. 比起权限的组合,继承似乎更容易理解。子组继承父组的所有权限,规则来得更简单,同时意味着管理更容易。如何更进一步实现权限的继承?最直接的就是在 group 上引入“父子关系”。OK,再看,这基本上就是我的 group 继承方案了。

4. 更进一步,关于 permission 的细化与改进。容后再谈。

jackyz
2002-12-09 21:04
今天太忙,到现在才有时间来谈“容后再谈”的 permission 的扩展。

假设我们开发了一个粗粒度的权限控制框架软件,现在将它作为一个组件提供给用户,需要提供对外的接口。那么,如何提供扩展能力?或者说,在上面讨论的这样一个比较粗糙的权限控制系统中,在哪一个概念上进行扩展会比较合适?

考虑一下“who + what(which) + how ”的概念公式,什么部分是会常常需要扩展的呢?可以用排除法,毫无疑问,有了用户/权限管理的基本框架,who(User/Group) 的概念是不会经常需要扩展的。变化的可能是系统中引入新的 what (新的 resource 类型)或者新的 how (新的操作方式)。那么,排除了 User 和 Group 之后,在三个基本概念中,仅在 permission 上进行扩展是否足够?我们可以先考虑这样的设计中 permission 实质上映射了应用中的什么概念?第一感觉是 permission 解决了 how 的问题,即,这个概念表示了“怎样”的操作。

那么,这个“怎样”是在哪一个层次上的定义呢?个人认为,将 permission 定义在“商业方法”级别可能会比较合适,注意,这里可能是我与大家方案区别巨大的另一个重点之所在。

比方,发布、购买、取消。每一个商业方法可以意味着用户进行的一个“动作”(用例?maybe)。这些操作更高级的概念可能是信息发布子系统,商品销售子系统,订单管理子系统,在这个级别上定义 permission 在某些情况下或许也是合理的,但显然不够灵活。

在更低的级别上,或许可以设想在某些表(或者信息字段)的 CRUQ (增删改查)动作上定义 permission 概念。再通过这些权限的组合与交叉,用更少的 permission 表述复杂的操作。这往往是很多程序员最直接的想法(也是我以前对于权限定义的想法)。初一看,似乎简单明了,而且好像能够简化维护工作。但,实际上,这样的定义方式不仅过于繁琐,仅就功能而言也是不足取的。

一方面,这样的定义方式不足够完成更复杂的权限辨别。比方,一个购买操作,就可能意味着对系统中 N 个表的检索,更新,乃至删除。在这样的情况下,对多个表的 permission 的组合将会极其令人痛苦,因为基本上,所有可能用到的操作都必须开放给每一个可能要进行这种操作的用户。

另一方面,这样的方式因为将权限深入到了系统的数据访问层,使用起来将极其不便。也就是说,在这样的定义模式下,每一个数据访问方法都必需要知道当前的操作者是谁――在数据访问逻辑中必需要知道用户身份,这不仅让人费解,而且让人沮丧(实在惭愧,这样的设计本人就做过)。

而,定义在商业逻辑的层次上,一方面保证了数据访问代码的“纯洁性”,另一方面在功能上也是“足够”的。也就是说,对更低层次,能自由的访问数据,对更高层次,也能比较精细的控制权限。

确定了 permission 定义的合适层次,更进一步,我们能够发现 permission 实际上还隐含了 what 的概念。也就是说,对于 what 的 how 操作才会是一个完整的 permission 。只要确定了 permission 的定义层次,这一点并不难了解。比方,“发布”操作,隐含了“信息”的“发布”概念,而对于“商品”而言发布操作是没有意义的。同样的,“购买”操作,隐含了“商品”的“购买”概念。这里的绑定还体现在大量通用的同名的操作上,比方,你需要区分“商品的删除”与“信息的删除”这两个同名为“删除”的不同操作。

注:我方案中将“ permission + resource ”合起来的概念称作一个“ Operate ”以示区别。更复杂的系统或许会需要一个“permission + resource 关联”方案的实现,但这样的实现应该说与两者合并的方案在大的概念上并无二致,其中的必要和实现,这里不做讨论。

回到我们的问题――如何提供权限系统的扩展能力?我的答案应该比较明确了,是在 Operate (Resource + Permission) 的概念上进行扩展。那么具体来说应该怎么样的模式来实现扩展呢?

或许如同 GoF 著名的 Design Pattern 中提到的 Proxy 模式会是一个非常棒的方式。我的实现大致是这样的:在商业方法层(EJB Session Facade [Stateful sessionbean]中),取得该商业方法的 methodname ,再根据 classname 和 methodname 检索 operate 数据,然后依据这个 operate 信息和 stateful 中保存的 user 信息判断当前用户是否具备该方法的操作权限。个人认为 Banq 提供的“基于动态 proxy 的权限实现方法”要更优雅一些。 :)

cc
2002-12-10 10:29
是不是这样的?

public class Message{
.............
    public List void QueryByName(String name){
............
       return ResultList;
    } 
}

public class MessageProxy extends Message{
     private Message message;
     private User user;
     private Permission permission;
.............
     public List QueryByName(String name){
        if (permission.getPermission("类名","方法名",user)){
            return message.QueryByName(String name);
        }else{
            return null;
        }       
     }
}
<p>

的确啊,Role这个类看来似乎作用很小。其实,你把permission也role合在一起了。

猜你喜欢
12Go 1 2 3 4 ... 12 下一页