较直观,而且也足够灵活。 Role 对系统的贡献实质上就是提供了一个比较粗颗粒的分配单位。
Group 与 Operator 是多对多的关系。各概念的关系图示如下:
解释 :
Operator 的定义包括了 Resource Type 和 Method 概念。即, What 和 How 的概念。之所以将 What 和 How 绑定在一起作为一个 Operator 概念而不是分开建模再建立关联,这是因为很多的 How 对于某 What 才有意义。比如,发布操作对新闻对象才有意义,对用户对象则没有意义。
How 本身的意义也有所不同,具体来说,对于每一个 What 可以定义 N 种操作。比如,对于合同这类对象,可以定义创建操作、提交操作、检查冲突操作等。可以认为, How 概 念对应于每一个商业方法。其中,与具体用户身份相关的操作既可以定义在操作的业务逻辑之中,也可以定义在操作级别。比如,创建者的浏览视图与普通用户的浏 览视图要求内容不同。既可以在外部定义两个操作方法,也可以在一个操作方法的内部根据具体逻辑进行处理。具体应用哪一种方式应依据实际情况进行处理。
这样的架构,应能在易于理解和管理的情况下,满足绝大部分粗粒度权限控制的功能需要。但是除了粗粒度权限,系统中必然还会包括无数对具体 Instance 的细粒度权限。这些问题,被留给业务逻辑来解决,这样的考虑基于以下两点:
一 方面,细粒度的权限判断必须要在资源上建模权限分配的支持信息才可能得以实现。比如,如果要求创建者和普通用户看到不同的信息内容,那么,资源本身应该有 其创建者的信息。另一方面,细粒度的权限常常具有相当大的业务逻辑相关性。对不同的业务逻辑,常常意味着完全不同的权限判定原则和策略。相比之下,粗粒度 的权限更具通用性,将其实现为一个架构,更有重用价值;而将细粒度的权限判断实现为一个架构级别的东西就显得繁琐,而且不是那么的有必要,用定制的代码来 实现就更简洁,更灵活。
所以细粒度控制应该在底层解决, Resource 在实例化的时候,必需指定 Owner 和 GroupPrivilege 在对 Resource 进行操作时也必然会确定约束类型:究竟是 OwnerOK 还是 GroupOK 还是 AllOK 。 Group 应和 Role 严格分离 User 和 Group 是多对多的关系, Group 只用于对用户分类,不包含任何 Role 的意义; Role 只授予 User ,而不是 Group 。如果用户需要还没有的多种 Privilege 的组合,必须新增 Role 。 Privilege 必须能够访问 Resource ,同时带 User 参数,这样权限控制就完备了。
思想 :
权限系统的核心由以下三部分构成: 1. 创造权限, 2. 分配权限, 3. 使用权限,然后,系统各部分的主要参与者对照如下: 1. 创造权限 - Creator 创造, 2. 分配权限 - Administrator 分配, 3. 使用权限 - User :
1. Creator 创造 Privilege , Creator 在设计和实现系统时会划分,一个子系统或称为模块,应该有哪些权限。这里完成的是 Privilege 与 Resource 的对象声明,并没有真正将 Privilege 与具体 Resource 实例联系在一起,形成 Operator 。
2. Administrator 指定 Privilege 与 Resource Instance 的关联。在这一步, 权限真正与资源实例联系到了一起, 产生了 Operator ( Privilege Instance )。 Administrator 利用 Operator 这个基本元素,来创造他理想中的权限模型。如,创建角色,创建用户组,给用户组分配用户,将用户组与角色关联等等 ... 这些操作都是由 Administrator 来完成的。
3. User 使用 Administrator 分配给的权限去使用各个子系统。 Administrator 是用户,在他的心目中有一个比较适合他管理和维护的权限模型。于是,程序员只要回答一个问题,就是什么权限可以访问什么资源,也就是前面说的 Operator 。程序员提供 Operator 就意味着给系统穿上了盔甲。 Administrator 就可以按照他的意愿来建立他所希望的权限框架 可以自行增加,删除,管理 Resource 和 Privilege 之间关系。可以自行设定用户 User 和角色 Role 的对应关系。 ( 如果将 Creator 看作是 Basic 的发明者, Administrator 就是 Basic 的使用者,他可以做一些脚本式的编程 ) Operator 是这个系统中最关键的部分,它是一个纽带,一个系在 Programmer , Administrator , User 之间的纽带。
用一个功能模块来举例子。
一.建立角色功能并做分配:
1 .如果现在要做一个员工管理的模块 ( 即 Resources) ,这个模块有三个功能,分别是:增加,修改,删除。给这三个功能各自分配一个 ID ,这个 ID 叫做功能代号:
Emp_addEmp , Emp_deleteEmp , Emp_updateEmp 。
2 .建立一个角色 (Role) ,把上面的功能代码加到这个角色拥有的权限中,并保存到数据库中。角色包括系统管理员,测试人员等。
3 .建立一个员工的账号,并把一种或几种角色赋给这个员工。比如说这个员工既可以是公司管理人员,也可以是测试人员等。这样他登录到系统中将会只看到他拥有权限的那些模块。
二.把身份信息加到 Session 中。
登录时,先到数据库中查找是否存在这个员工,如果存在,再根据员工的 sn 查找员工的权限信息,把员工所有的权限信息都入到一个 Hashmap 中,比如就把上面的 Emp_addEmp 等放到这个 Hashmap 中。然后把 Hashmap 保存在一个 UserInfoBean 中。最后把这个 UserInfoBean 放到 Session 中,这样在整个程序的运行过程中,系统随时都可以取得这个用户的身份信息。
三.根据用户的权限做出不同的显示。
可以对比当前员工的权限和给这个菜单分配的“功能 ID ”判断当前用户是否有打开这个菜单的权限。 例如:如果保存员工权限的 Hashmap 中没有这三个 ID 的任何一个,那这个 菜 单就不会显示,如果 员工 的 Hashmap 中有任何一个 ID ,那这个 菜 单都会显示。
对于一个新闻系统 (Resouce) ,假设它有这样的功能 (Privilege) :查看,发布,删除,修改;假设对于删除,有 " 新闻系统管理者只能删除一月前发布的,而超级管理员可删除所有的这样的限制,这属于业务逻辑 (Business logic) ,而不属于用户权限范围。也就是说权限负责有没有删除的 Permission ,至于能删除哪些内容应该根据 UserRole or UserGroup 来决定 ( 当然给 UserRole or UserGroup 分配权限时就应该包含上面两条业务逻辑 ) 。
一 个用户可以拥有多种角色,但同一时刻用户只能用一种角色进入系统。角色的划分方法可以根据实际情况划分,按部门或机构进行划分的,至于角色拥有多少权限, 这就看系统管理员赋给他多少的权限了。用户—角色—权限的关键是角色。用户登录时是以用户和角色两种属性进行登录的(因为一个用户可以拥有多种角色,但同 一时刻只能扮演一种角色),根据角色得到用户的权限,登录后进行初始化。这其中的技巧是同一时刻某一用户只能用一种角色进行登录。
针对不同的“角色”动态的建立不同的组, 每个项目建立一个单独的 Group ,对于新的项目,建立新的 Group 即可。在权限判断部分,应在商业方法上予以控制。比如:不同用户的“操作能力”是不同的 ( 粗粒度的控制应能满足要求 ) ,不同用户的“可视区域”是不同的 ( 体现在对被操作的对象的权限数据,是否允许当前用户访问,这需要对业务数据建模的时候考虑权限控制需要 ) 。
扩展性:
有了用户 / 权限管理的基本框架, Who(User/Group) 的概念是不会经常需要扩展的。变化的可能是系统中引入新的 What ( 新的 Resource 类型 ) 或者新的 How( 新的操作方式 ) 。那在三个基本概念中,仅在 Permission 上进行扩展是不够的。这样的设计中 Permission 实质上解决了 How 的问题,即表示了“怎样”的操作。那么这个“怎样”是在哪一个层次上的定义呢?将 Permission 定 义在“商业方法”级别比较合适。比如,发布、购买、取消。每一个商业方法可以意味着用户进行的一个“动作”。定义在商业逻辑的层次上,一方面保证了数据访 问代码的“纯洁性”,另一方面在功能上也是“足够”的。也就是说,对更低层次,能自由的访问数据,对更高层次,也能比较精细的控制权限。
确定了 Permission 定义的合适层次,更进一步,能够发现 Permission 实际上还隐含了 What 的概念。也就是说,对于 What 的 How 操作才会是一个完整的 Operator 。 比如,“发布”操作,隐含了“信息”的“发布”概念,而对于“商品”而言发布操作是没有意义的。同样的,“购买”操作,隐含了“商品”的“购买”概念。这 里的绑定还体现在大量通用的同名的操作上,比如,需要区分“商品的删除”与“信息的删除”这两个同名为“删除”的不同操作。
提供权限系统的扩展能力是在 Operator (Resource + Permission) 的概念上进行扩展。 Proxy 模式是一个非常合适的实现方式。实现大致如下:在业务逻辑层 (EJB Session Facade [Stateful SessionBean] 中 ) ,取得该商业方法的 Methodname ,再根据 Classname 和 Methodname 检索 Operator 数据,然后依据这个 Operator 信息和 Stateful 中保存的 User 信息判断当前用户是否具备该方法的操作权限。
应用在 EJB 模式下,可以定义一个很明确的 Business 层次,而一个 Business 可能意味着不同的视图,当多个视图都对应于一个业务逻辑的时候,比如, Swing Client 以及 Jsp Client 访问的是同一个 EJB 实现的 Business 。在 Business 层上应用权限较能提供集中的控制能力。实际上,如果权限系统提供了查询能力,那么会发现,在视图层次已经可以不去理解权限,它只需要根据查询结果控制界面就可以了。
灵活性 :
Group 和 Role ,只是一种辅助实现的手段,不是必需的。如果系统的 Role 很多,逐个授权违背了“简单,方便”的目的,那就引入 Group ,将权限相同的 Role 组成一个 Group 进行集中授权。 Role 也一样,是某一类 Operator 的集合,是为了简化针对多个 Operator 的操作。
Role 把具体的用户和组从权限中解放出来。一个用户可以承担不同的角色,从而实现授权的灵活性。当然, Group 也可以实现类似的功能。但实际业务中, Group 划分多以行政组织结构或业务功能划分;如果为了权限管理强行将一个用户加入不同的组,会导致管理的复杂性。
Domain 的应用。为了授权更灵活,可以将 Where 或者 Scope 抽象出来,称之为 Domain ,真正的授权是在 Domain 的范围内进行,具体的 Resource 将分属于不同的 Domain 。 比如:一个新闻机构有国内与国外两大分支,两大分支内又都有不同的资源(体育类、生活类、时事政治类)。假如所有国内新闻的权限规则都是一样的,所有国外 新闻的权限规则也相同。则可以建立两个域,分别授权,然后只要将各类新闻与不同的域关联,受域上的权限控制,从而使之简化。
权限系统还应该考虑将功能性的授权与资源性的授权分开。很多系统都只有对系统中的数据(资源)的维护有权限控制,但没有对系统功能的权限控制。
权限系统最好是可以分层管理而不是集中管理。大多客户希望不同的部门能且仅能管理其部门内部的事务,而不是什么都需要一个集中的 Administrator 或 Administrators 组来管理。虽然你可以将不同部门的人都加入 Administrators 组,但他们的权限过大,可以管理整个系统资源而不是该部门资源。
正向授权与负向授权:正向授权在开始时假定主体没有任何权限,然后根据需要授予权限,适合于权限要求严格的系统。负向授权在开始时假定主体有所有权限,然后将某些特殊权限收回。
权限计算策略:系统中 User , Group , Role 都可以授权,权限可以有正负向之分,在计算用户的净权限时定义一套策略。
系统中应该有一个集中管理权限的 AccessService ,负责权限的维护(业务管理员、安全管理模块)与使用(最终用户、各功能模块),该 AccessService 在实现时要同时考虑一般权限与特殊权限。虽然在具体实现上可以有很多,比如用 Proxy 模式,但应该使这些 Proxy 依赖于 AccessService 。各模块功能中调用 AccessService 来检查是否有相应的权限。所以说,权限管理不是安全管理模块自己一个人的事情,而是与系统各功能模块都有关系。每个功能模块的开发人员都应该熟悉安全管理模块,当然,也要从业务上熟悉本模块的安全规则。
技术实现 :
1 .表单式认证,这是常用的,但用户到达一个不被授权访问的资源时, Web 容器就发
出一个 html 页面,要求输入用户名和密码。
2 .一个基于 Servlet Sign in/Sign out 来集中处理所有的 Request ,缺点是必须由应用程序自己来处理。
3 .用 Filter 防止用户访问一些未被授权的资源, Filter 会截取所有 Request/Response ,
然后放置一个验证通过的标识在用户的 Session 中,然后 Filter 每次依靠这个标识来决定是否放行 Response 。
这个模式分为:
Gatekeeper :采取 Filter 或统一 Servlet 的方式。
Authenticator : 在 Web 中使用 JAAS 自己来实现。
用户资格存储 LDAP 或数据库:
1. Gatekeeper 拦截检查每个到达受保护的资源。首先检查这个用户是否有已经创建
好的 Login Session ,如果没有, Gatekeeper 检查是否有一个全局的和 Authenticator 相关的 session ?
2. 如果没有全局的 session ,这个用户被导向到 Authenticator 的 Sign-on 页面,
要求提供用户名和密码。
3. Authenticator 接受用户名和密码,通过用户的资格系统验证用户。
4. 如果验证成功, Authenticator 将创建一个全局 Login session ,并且导向 Gatekeeper
来为这个用户在他的 web 应用中创建一个 Login Session 。
5. Authenticator 和 Gatekeepers 联合分享 Cookie ,或者使用 Tokens 在 Query 字符里。