级别: 中级 Robert Patt-Corner (rpatt-corner@proqual.net), 首席技术官, Proqual-IT
2008 年 6 月 12 日
尝试对访问应用程序资源进行细粒度控制的 Java™ 开发人员很快会受到内置 Java Platform Enterprise Edition (Java EE) 声明式授权的限制。本系列有关基于 Enum 的授权 (EAz) 的文章共包含三个部分,第 1 部分介绍了 Java 5 中的完整访问控制列表 (ACL) 实现的基本体系结构。第 2 部分介绍了 EAz 授权解决方案的使用和实现。第 3 部分是本系列文章的最后一部分,阐述用于将 EAz 集成到图形界面中的技术,对使用 Web 应用程序的问题实施 JavaServer™ Faces 解决方案,以图形方式表示哪些离散权限可用于 Web 框架中的用户和组。
来自 IBM WebSphere Developer Technical Journal.
引言
基于 Enum 的授权(EAz,读作“easy”)系列提供了在 Java 5 中实现完整的访问控制列表 (ACL) 实现的方法,其中包括离散的二进制权限、任意命名的权限集、对受保护的资源应用和快速测试访问控制的方法,以及使用 JavaServer Faces (JSF) 显示特定访问控制的真值表的方法。
本系列文章的第 1 部分介绍了 Java 5 中的完整访问控制列表 (ACL) 实现的基本体系结构,第 2 部分在第 1 部分提供的示例基础上,介绍了 EAz 授权解决方案的使用和实现。本文将继续介绍虚拟校园场景,用于对本系列前面的文章进行阐述,说明如何通过 EAz 实现复杂的授权模型。请参阅以前的文章,以了解关于此解决方案的发展的更多详细信息。
回顾示例场景
如下所列,图 1 重申了虚拟校园布局,表 1 描述了示例中包含的组和个体,图 2 显示了您将在本文中使用 EAz 框架实现的 ACL。
图 1 示例虚拟校园布局
表 1. 虚拟校园示例的组和个体
| 标识 | 类型 | 组 |
|---|
| 校园 A 的用户 | 组 | --- | | 校园 B 的用户 | 组 | --- | | 校园 A 的工程师(工程师) | 组 | --- | | 校园 A 的生物学家(生物学家) | 组 | --- | | 校园 A 的清洁工 | 组 | --- | | Jane Linnaeus | 个体 | 校园 A 的用户、生物学家 | | Jim Fermi | 个体 | 校园 A 的用户、工程师 | | Stan the Yeti | 个体 | 校园 A 的用户、生物学家 | | Ken Lay | 个体 | 校园 A 的用户、校园 A 的清洁工 | | Danny Dafoe | 个体 | 校园 A 的用户 | | Carla Bonheur | 个体 | 校园 B 的用户 |
与第 1 部分一样,假设以下访问规则:
- 只有校园 A 的用户可以进入校园 A;只有校园 B 的用户可以进入校园 B。
并且在校园 A 的用户中:
- 生物学家和工程师可以进入自己的大楼和其中的公共空间;另外,生物学家可以进入工程楼。
- 校园 A 的清洁工可以进入任何房间(实验室除外)。
- 允许 Jim Fermi 进入工程楼中他自己的办公室,即个人办公室 2。
- 只允许 Jim Fermi 和 Stan the Yeti 进入工程楼中的安全实验室 3。
- 允许 Jane Linnaeus 进入生物楼中她自已的个人办公室 5,
- 只有 Stan the Yeti 可以进入生物楼的安全实验室 6。
校园、大楼和房间的保护情况如图 2 所示。
图 2. 虚拟校园示例的 ACL 结构
请参见第 1 部分以了解有关虚拟校园示例、EAz 操作概念和主要 EAz 体系结构的其他详细信息。
授权信息的图形显示的要求
任何具有强大功能的授权框架都将使应用程序能够实现一组复杂且丰富的授权规则。了解并维护这些规则可能是极其困难的,除非您有办法以简洁的方式直观地显示这些规则并且能以对纯编程结构(如访问控制列表 (ACL))的最小引用访问这些规则。以下两种类型的显示特别有用:
- 可对受保护的资源执行的操作,以及配置为执行每项操作的用户或组的显示。
- 配置为对资源执行任何操作的用户或组,以及配置为由每个用户或组执行的操作的显示。
表 2 显示了虚拟校园示例操作的典型显示,表 3 显示了用户和组。在本文其余的部分中,术语参与者是指命名用户或组的任何组合。
表 2. 配置操作与原始 ACL
| ACL | ACTION_ENTER |
|---|
| campusA | CAMPUS_A_USERS | | engrBldg | CAMPUS_A_CLEANERS CAMPUS_A_BIOLS CAMPUS_A_ENGRS | | engrBldgRoom2 | PRINCIPAL/Jim Fermi CAMPUS_A_CLEANERS | | engrBldgSecureLab3 | PRINCIPAL/Jim Fermi PRINCIPAL/Stan the Yeti | | biolBldg | CAMPUS_A_CLEANERS CAMPUS_A_BIOLS | | biolBldgRoom5 | CAMPUS_A_CLEANERS PRINCIPAL/Jane Linnaeus | | biolBldgSecureLab6 | PRINCIPAL/Stan the Yeti |
表 3. 配置参与者与原始 ACL
| ACL | CAMPUS_A_ USERS | CAMPUS_A_ CLEANERS | CAMPUS_A_ BIOLS | CAMPUS_A_ ENGRS | PRINCIPAL/ Jim Fermi | PRINCIPAL/ Stan the Yeti | PRINCIPAL/ Jane Linnaeus |
|---|
| campusA | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldg | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ ENTER | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldgRoom2 | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldgSecureLab3 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ ENTER | ACTION_ NO_ACTION | | biolBldg | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | biolBldgRoom5 | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | biolBldgSecureLab6 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION |
正如您所看到的,上面两个表显示了相同的数据,但却向观察员传递了截然不同的信息。表 2 是以列中显示操作的形式组织的,让您能够快速了解哪些人配置为可以执行给定的操作,而表 3 是以列中显示参与者的形式组织的,让您能够快速了解参与者配置为可以执行哪些操作。上面这两个表都是由本文中介绍的 EAz 可视化框架生成的。
能够生成此类表并对这些表进行操作对于任何授权框架来说都是一项关键功能。
配置和权限
配置和权限并不是一回事。例如,在表 3 中,Jim Fermi 配置为可以进入多个房间和实验室,但是未配置为可以进入校园。从保护资源安全的各个 ACL 的角度来看,这是完全正确的。但是,ACL 只是授权过程的一部分。
几乎每个授权框架(EAz 也不例外)都需要借助某种自定义注册中心来定义框架所属的用户和组。表 3 并未显示 Jim 的组成员关系,仅显示了在其中对他进行了配置的 ACL。在 campusA ACL 行中出现在他的姓名旁边的 ACTION_NO_ACTION 只是表明他没有配置在 campusA ACL 中,但是他完全可以凭借另一个组的成员的身份进入校园大门。
正是组成员关系与 ACL 配置的这种分离使得框架灵活且高效:您按 ACL 中的异常进行管理,只有在 Jim 需要超出通过其组成员关系授予的身份验证时才会明确提到他的名字。
同样的情况也适用于组。虽然 CAMPUS_A_CLEANERS 组配置为可进入工程楼和生物楼以及除安全实验室之外的所有配置房间,但是它未配置为可进入校园。本示例假定存在一个允许多种组成员关系的注册中心,同时本示例期望各校园清洁工成为 CAMPUS_A_USERS 和 CAMPUS_A_CLEANERS 组的成员。一个组让他们能够进入校园大门;另一个组让他们能够进入建筑物。回过头来看表 1 中的原始配置,其中以 Ken Lay 为例向我们展示了具有双重组成员身份的清洁人员。
因此,您需要第三种视图,在该视图中参与者与操作的排列组合显示了指定参与者在与 ACL 交互时的权限,考虑到了他们在注册中心的组成员关系。下面的表 4 和表 5 中显示了不同的排列组合之间的区别。
我们还记得,在前面的文章(和表 1)中,我们已经为执行此类测试设定了三个人:
- Daniel Dafoe,他是 CampusA 的一名学生,没有任何附加权限。
- Carla Bonheur,她是一位教授,到处进行演讲,需要带人进入任何校门和建筑物。
- Ken Lay,是一名校园清洁工,可以进入除安全实验室之外的任何地方。
表 4 和表 5 中的信息显示了注册中心和 ACL 配置共同作用的结果,分别呈现了操作和参与者组织的信息。而且,需要一个 ACL 框架以便能够生成这些类型的有用的显示,并且能够从 EAz 可视化框架中生成这些表格本身。由于这些显示给出了各种权限的总体视图而不是纯 ACL 配置信息,因此这些显示将被称为权限显示。
表 4. 权限操作(Lay、Dafoe 和 Bonheur)与原始 ACL
| ACL | ACTION_ENTER | ACTION_NO_ACTION |
|---|
| campusA | Danny Dafoe Ken Lay | Carla Bonheur | | engrBldg | Ken Lay | NO ACTORS | | engrBldgRoom2 | Ken Lay | NO ACTORS | | biolBldg | Ken Lay | NO ACTORS | | biolBldgRoom5 | Ken Lay | NO ACTORS |
表 5. 权限参与者(Lay、Dafoe 和 Bonheur)与原始 ACL
| ACL | Danny Dafoe | Carla Bonheur | Ken Lay |
|---|
| campusA | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ ENTER | | engrBldg | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | engrBldgRoom2 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | biolBldg | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | biolBldgRoom5 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER |
在权限表中,ACTION_NO_ACTION 条目表示完全拒绝授权,这是因为考虑到了注册中心信息。
通过这四种类型的显示(每种显示中都提供了参与者和操作变体的 ACL 配置和总体权限),可以很容易地理解一个授权框架及其预期的操作。
复杂的示例
让我们看下面一个示例,该示例增加了这一情形的复杂性,而与此同时也显示了 EAz 框架的灵活性。假定您:
- 将 engrBldgSecureLab3 向 CAMPUS_A_ENGRS 组的所有成员开放,其中包括 Jim Fermi,但是不包括 Stan the Yeti。
- 授予 Jim Fermi 在任何时间锁住实验室门的权限,以便他能在不受打扰的情况下工作。
- 只允许 Ken Lay 进入打扫实验室,整理工程师离开后留下的混乱环境。
在属性文件中使用 EAz 和我们的 mock 注册中心,您可以在几分钟内完成此更改:在生产场景中,注册将会出现在 LDAP 或数据库中,因此,能够更方便地通过一个适当的注册中心用户界面来实现。
如果只有 Jim Fermi 和 Stan the Yeti 配置为进入工程楼中的安全实验室 3,则工程楼中的安全实验室 3 的 ACL 属性文件将如清单 1 中所示。
清单 1. 工程楼中的安全实验室 3 的原始 ACL
# Format is groupOrPrincipal=permission
PRINCIPAL/Stan\ the\ Yeti=PERM_ENTER
PRINCIPAL/Jim\ Fermi=PERM_ENTER |
若要应用我们的更改,您需要将此文件修改为如清单 2 中所示。
清单 2. 工程楼中的安全实验室 3 的修订后的 ACL
# Format is groupOrPrincipal=permission
PRINCIPAL/Stan\ the\ Yeti=PERM_ENTER
PRINCIPAL/Jim\ Fermi=PERM_LOCK_DOOR
PRINCIPAL/Ken\ Lay=PERM_ENTER
CAMPUS_A_ENGRS=PERM_ENTER |
您需要对显示进行更改以反映更改的授权模式,更改后的显示应如表 6 中所示。
表 6. 配置操作与修订后的 ACL(更改以粗体显示)
| ACL | ACTION_ENTER | ACTION_LOCK_DOOR |
|---|
| campusA | CAMPUS_A_USERS | NO_ACTORS | | engrBldg | CAMPUS_A_CLEANERS CAMPUS_A_BIOLS CAMPUS_A_ENGRS | NO_ACTORS | | engrBldgRoom2 | PRINCIPAL/Jim Fermi CAMPUS_A_CLEANERS | NO_ACTORS | | engrBldgSecureLab3 |
PRINCIPAL/Ken Lay
PRINCIPAL/Stan the Yeti
CAMPUS_A_ENGRS
|
PRINCIPAL/Jim Fermi
| | biolBldg | CAMPUS_A_CLEANERS CAMPUS_A_BIOLS | NO_ACTORS | | biolBldgRoom5 | CAMPUS_A_CLEANERS PRINCIPAL/Jane Linnaeus | NO_ACTORS | | biolBldgSecureLab6 | PRINCIPAL/Stan the Yeti | NO_ACTORS |
表 7. 配置参与者与修订后的 ACL(更改以粗体显示)
| ACL | CAMPUS_A_ USERS | CAMPUS_A_ CLEANERS | CAMPUS_A_ BIOLS | CAMPUS_A_ ENGRS | PRINCIPAL/ Jim Fermi | PRINCIPAL/ Ken Lay | PRINCIPAL/ Stan the Yeti | PRINCIPAL/ Jane Linnaeus |
|---|
| campusA | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldg | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ ENTER | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldgRoom2 | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | engrBldgSecureLab3 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION |
ACTION_ ENTER
|
ACTION_ LOCK_DOOR
|
ACTION_ ENTER
| ACTION_ ENTER | ACTION_ NO_ACTION | | biolBldg | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | | biolBldgRoom5 | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | biolBldgSecureLab6 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | ACTION_ NO_ACTION |
表 8. 权限操作表(Lay、Dafoe 和 Bonheur)与修订后的 ACL(更改以粗体显示)
| ACL | ACTION_ENTER | ACTION_NO_ACTION |
|---|
| campusA | Danny Dafoe Ken Lay | Carla Bonheur | | engrBldg | Ken Lay | NO ACTORS | | engrBldgRoom2 | Ken Lay | NO ACTORS | | biolBldg | Ken Lay | NO ACTORS | | engrBldgSecureLab3 |
Ken Lay
| NO ACTORS | | biolBldgRoom5 | Ken Lay | NO ACTORS |
表 9. 权限参与者(Lay、Dafoe 和 Bonheur)与修订后的 ACL(更改以粗体显示)
| ACL | Danny Dafoe | Carla Bonheur | Ken Lay |
|---|
| campusA | ACTION_ ENTER | ACTION_ NO_ACTION | ACTION_ ENTER | | engrBldg | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | engrBldgRoom2 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | engrBldgSecureLab3 | ACTION_ NO_ACTION | ACTION_ NO_ACTION |
ACTION_ ENTER
| | biolBldg | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER | | biolBldgRoom5 | ACTION_ NO_ACTION | ACTION_ NO_ACTION | ACTION_ ENTER |
与前面的表中一样,上述所有信息都是使用显示框架通过 EAz 生成的。engrBldgSecureLab3 ACL 中仅列出了必需的更改。
图形显示的透视方法
文本表(如表 2 至表 9)对于显示和理解应用程序的授权策略非常重要,但是基于 Web 的显示将更为有用,因为它们可能会实际修改 ACL 和注册中心成员关系。
开发维护 UI 过程中遇到的主要技术挑战是获取可靠且可维护的组合以及以 Web 形式呈现配置和权限表,如表 2 到表 9。在得到 ACL 配置或权限结构的内存中表示形式,以及找到可以将这种表示形式转为 Web 显示之后,理解当前策略的工作就完成了,维护工作也得到了极大简化,包括:
- 以读写图形字段替换仅供显示的图形字段。
- 编写将用于解释 UI 中的更改并相应修改 ACL 和注册中心的事件处理程序和服务方法。
接下来是描述使用 JavaServer Faces (JSF) 对权限和配置表的显示进行可视化处理并创建该显示的技术。可以对此实现进行增强来以简单明了的方式创建完整的 ACL 维护框架,具体做法是实现上面所述的图形字段更改和服务方法。
透视图的概念
可视化框架背后的主要概念是透视图,这一概念来源于 Eclipse 图形开发环境的实现。在 Eclipse 中,透视图以一种不同的视图展示了相同的底层对象。图 3 和图 4 以我们自己的 EAz 为例,分别显示了可视化框架的 Web 透视图和 Java 透视图。
图 3. EAuthZWeb 项目的 Eclipse Web 透视图
图 4. EAuthZWeb 项目的 Eclipse Java 透视图
在每个透视图中,都重点强调了构成此项目的基础文件集的某些方面,对另一些方面进行了重新安排,同时还降低了一些方面的重要性或将其完全隐藏。这两个透视图均以相比较而言类似的方式显示了 Java 源代码,但是 Web 透视图显示了针对 Web 部署描述符的特殊条目,而 Java 透视图显示了有关各种库集合的损坏和位置的更详细的信息。
EAz 框架实现可视化的方法是类似的,就是将一组 ACL 封装到一个 ACLPerspective 对象中,此方法将通过一种便于实现可视化的方式对 ACL 的内容进行重新安排。这是装饰模式的一种典型的应用,事实上 ACLPerspective 的原始名称为 ACLDecorator,而此对象最终将多个 ACL 组合在一起并提供了一个针对多个 ACL 的视角,因此认为此对象仅封装了单个 ACL 是不恰当的。
ACLPerspective 的设计
图 5 显示了 ACLPerspective 对象结构的概述。首先看一下 ACLPerspective 的公共方法,ACLPerspective 提供了三种版本的构造函数。它可以深入查看以下内容:
-
一系列 ACL。此构造函数提供了一个配置透视图,其中每个显示元素都与列表中的 ACL 的配置直接相关,无需引用注册中心或其他外部实体。此构造函数用于生成表 2 和表 3 中所示的配置显示。
-
单个 ACL。这是用于负责处理单个 ACL 的配置透视图的方便方法。
-
一系列 ACL 和一系列 MockPrincipal。
第 2 部分引入了 MockPrincipal,将其作为一种可封装用户和组(例如,参与者)的非 Java EE 注册中心概念的方法。MockPrincipal 可以表示个人及其组成员关系,也可以表示组。第三种构造函数用于构造表 4 和表 5 中的显示所用的权限样式的透视图。使用此权限显示构造函数,调用方提供了一组 MockPrincipal,而透视图提供了这些 MockPrincipal 与 ACL 列表中的 ACL 之间的交互视图,考虑到了它们的组成员关系。
ACLPerspective 的四种公共方法为调用方提供了实际视图和底层数据结构。两个 getAcl..Table() 方法(一个用于面向参与者的表,另一个用于面向操作的表)为表 2 至表 9 这八个表提供了矩阵格式的信息。两个 getACL...Map() 方法提供了对自定义显示的内部数据结构的访问。
图 5. ACLPerspective 及其内部类概述
ActorRelationships 和 ActionRelationships 内部类
ACLPerspective 使用两个内部类从参与者(即,列中的参与者和单元中的操作)或操作(以及单元中的参与者)的角度将其 ACL 关系的知识封装起来。组织 ACL 关系的表示形式的主要工作大部分都是在这些内部类中进行的。
每个内部类都有一个能够添加适用于该类的关系和访问器 (getter) 来访问其在内部管理的关系的方法。例如,ActorRelationships 类中有一个能够添加参与者及其已配置或已允许的操作的方法,一个能够在实例中检索参与者的独特列表的方法,以及一个能够检索已允许或已配置的将参与者用作一个键的操作的方法。
内部类可以使用内部映射结构和一个集合实现这些功能。这些内部类之一的给定实例中的关系的范围是单个 ACL。例如,ActorRelationships 类的一个实例保存了针对保证特定 ACLHolder 资源的特定 ACL 的权限配置和测试的每个参与者的知识,并将每个参与者映射到每个参与者可以执行的操作列表。对于单个 ActionRelationships 来说反过来也是这样;单个 ActionRelationships 维护针对给定 ACL 上的权限配置或测试的每个操作的知识,将每个操作映射到单个 ACL 的上下文中该操作的授权参与者的列表。
参与者和操作映射
外部 ACLPerspective 类可以为一组 ACL 管理总体参与者和操作映射。这两个映射保存了用于构建授权视图的重要信息。正如您在图 5 中的类关系图中看到的,aclActorMap 包含透视图中每个 ACL 的 ActorRelationships 实例;aclActionMap 包含透视图中每个 ACL 的 ActionRelationships 实例。
这些映射可以并行进行维护,并且可以构建为类实例化过程中的一部分。这些映射将维护有关配置或权限视图的信息,具体取决于特定的 ACLPerspective 实例的构建方式;例如,如前面讨论过的,使用配置样式的构造函数或权限样式的构造函数。
因此,在构造之后,ACLPerspective 实例包含全面了解一组 ACL 的配置,或通过这些 ACL 向特定的一组参与者授予的权限所需的所有信息。
透视图表
ACLPerspective 中的映射可维护核心结构,但是不能直接用于您在此处需要的通用类型的信息表示。因此,ACLPerspective 使每个映射的表视图可用,以表格的形式显示了映射的内容。这些表是 getAclActorTable() 和 getAclActionTable() 访问器在运行时动态生成的,但是其效果与在构造期间创建的表和为提高效率而缓存的表是一样。
每个表都是表 2 至表 9 中所示的结构的文本表示,是作为二维矩阵 String[][] 提供的:
- getAclActorTable() 提供了一个表,顶部为参与者、一侧自上而下为 ACLHolder,单元中为操作。
- getAclActionTable() 提供了一张补充性的表,顶部为操作、一侧自上而下为 ACLHolder,单元中为参与者。
访问 ACLPerspective
要访问 ACLPerspective,以下两点(或三点,第三点是可选的)是必需的:
-
第 2 部分中介绍的 EAz 授权管理器的一个实例已经进行了增强,可以在 Eaz 的当前版本中构造和返回 ACLPerspective。
- 创建透视图所需的一个 ACL 或一组 ACL。
- 此外,要确定其权限的一组 MockPrincipal。如果您提供了 MockPrincipal,您将得到权限样式的透视图;如果未提供 MockPrincipal,您将接收到完全基于一个或多个 ACL 中的配置信息的配置透视图。
ACLPerspective 构造函数的 AuthorizationManager 实现是一个简单明了的包装方法。清单 3 显示了最丰富的示例,该示例用于面向权限的构造函数;其他方法也是类似的甚至更简单。
清单 3. 用于获取面向权限的 ACLPerspective 的 AuthorizationManagerImpl 方法
public ACLPerspective getACLPerspective(List<ACLHolder> aclHolderList,
List<MockPrincipal> specificActors) {
List<ACL> aclList = new ArrayList<ACL>();
for (ACLHolder aclHolder : aclHolderList) {
aclList.add(aclManager.getACL(aclHolder));
}
return new ACLPerspective(aclList, specificActors);
} |
使用 ACLPerspective
第 2 部分中的同一 JUnit 测试工具用于生成本文中的表;此流程显示了使用 ACLPerspective 的主要方面。清单 4 显示了 Junit 测试,该测试可以生成并输出显示一组 ACL 的配置信息的 aclActorTable。此测试用于生成表 3 和表 7。正如您所看到的,在获得了 ACLPerspective 之后,对它进行转储就如同打印其单元一样简单。
清单 4. 使用 ACLPerspective 生成 actor 表
@Test
public void testGetMatrixDefaultActors() {
ACLPerspective perspective = authorizationManager.getACLPerspective(aclHolderList);
String[][] actorTable = perspective.getAclActorTable();
for (String[] row : actorTable) {
for (String cell : row) {
printCell(cell);
}
System.out.println("\n");
}
System.out.println("=========================================================\n\n");
}
private void printCell(String cell){
int CELLSIZE=20;
StringBuffer sb = new StringBuffer(cell);
int pad = CELLSIZE-sb.length();
if (pad >0) {
for(int i=1;i<pad;i++){
sb.append(" ");
}
}
vSystem.out.print("["+sb.toString()+"]");
} |
ACLPerspective 及为它提供支持的所有增强都可以在本文包括的下载文件中找到。
在 JavaServer Faces 应用程序中使用 ACLPerspective
JavaServer Faces (JSF) 具有许多固有的优点和功能并且能够方便地替换图形组件,使其成为透视图显示方面首选的技术。由于用输入组件替换输出组件可以将与 ACL 相关的显示转换为 ACL 配置应用程序,因此对于与 ACL 相关的视图尤为重要,在与事件侦听器结合使用时还能用来记录解释并应用该技术的更改和服务方法。
 |
关于 JSF 的更多信息
有关 JavaServer Faces 的详细讨论超出了本文的范围。如果您对 JSF 还不熟悉,请参阅参考资料部分中有关 Rick Hightower 的 developerWorks 系列优秀文章和 Bauke Luitsen Scholtz (BalusC) 的 Blog 以获取丰富的 JSF 示例。 |
|
JSF 类似于基于共享内存的图形框架,如 AWT (Abstract Window Toolkit)、Swing 或 SWT (Standard Widget Toolkit)。像 AWT 这样的框架和其他一些框架均可在单台计算机s上运行,并且可以在基于内存中模型的人工界面上生成显示。框架可通过多种会影响模型的方式对界面上的事件作出响应,所有这些响应都是在工作站上的同一内存空间中完成的。
JSF 提供了一组服务器端组件,这些组件支持在对话的一端使用模型而在另一端使用呈现语言(如 HTML),从而能够有效地将工作站浏览器上的一些内存空间和其他一些内存空间分配给网络服务器。这一比喻并不贴切,但是它所传达的 JSF 的风格比我见过的其他比喻都要好。通常,您在服务器上运行的框架很可能是 Swing,当您希望显示信息或接收输入时会将其与 HTML(或任何其他标记语言)进行转换。
考虑到本文的目的,我们需要关注三种 JSF 构造:
-
JSP 页面,描述了如何使用 JSF 自定义标记显示您的内容。这将被称为 JSF 页面。
-
处理程序类,是一个有效的 JSF 支持 Bean,能够从 JSF 组件中构造实际图形视图。
-
配置文件,在 JSP 页面和处理程序之间进行通知,让它们相互了解,以及供处理程序用于了解其他 EAz framework。
JSF 页面通常包含大量标记或完全由标记构成;而 JSF 支持 Bean 非常简单,其存在的主要目的是对事件作出响应,使它们能与服务层进行通信。然而,在我们的示例应用程序中,由于您要显示动态表,因此需要逆转复杂性。例如,没办法事先获知 aclActorTable 或 aclActionTable 将包含多少列、多少行。由于只有一个相关的标记可用于处理动态行数(而不是动态列数),因此无法在 JSF 页面中通过标记有效地定义表。
项目组织
本示例中的代码被组织到三个 Eclipse 项目中,在图 3 和图 4 中可以看到:
- EAuthZ 继续包含核心 EAz 框架代码,包括服务方法、核心类和 ACLPerspective。
- EAuthZWeb 和 EAuthZWebEAR 项目可以根据需要实现用于访问和显示 ACLPerspective 的 JSF 应用程序。
本文的其余部分将重点介绍 EauthZWeb 项目及其在使 ACLPerspective 提供的 ACL 信息在 Web 上可用方面发挥的作用。
JSF 页面
清单 5 显示了完整的 JSF 页面。没有任何删减。以下代码行可生成一个 EAz 表的实际显示,可对您要显示的每个表的不同绑定重复执行:
<h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.actorTablePanel}" />
绑定将 JSF 标记(在本例中为 panelGroup,呈现为一个表)与指定支持 Bean(在本例中为您的 authZviewHandler)中的特定方法关联起来。标记中的说明能够有效地说明 JSF 框架:在生成 HTML 时,您需要使用此标记,在 faces-config.xml 中执行配置为 authZviewHandler 的类中的 getActorTablePanel() 方法,呈现此方法返回的 JSF PanelGroup 组件及其内容。
需要注意的是,这一动态呈现与大多数 JSF 技术是背道而驰的。更常见的情况是,标记详细、确切地告知 JSF 框架配置和呈现哪些 JSF 组件。不过,此处该页面中没有任何有关我们的表形状的线索,而处理程序能够提供这方面的线索,因此我们颠倒了顺序。
JSF 中提供了一个实际 DataTable 组件,该组件将让您能够创建希望的类型的表,并且可以在后台使用这一组件,您将在处理程序的讨论中看到此组件。但是,利用从 BalusC(请参阅参考资料)掌握的技术将 DataTable 从页面中隐藏,从而解决了动态数据表和 h:dataTable 标记的棘手问题。
h:dataTable 标记跨表中的各行实现了一个隐式循环,它需要一个 var 属性,可用在循环内对当前行进行访问。另一方面,您无法访问标记内的行内容,因此需要在处理程序中从头构建新表以对动态表形状作出响应。但是您可能希望能够通过单个处理程序来显示单个页面或一系列页面的潜在范围的广泛表。
如果要在您的 JSF 页面中使用 h:dataTable 标记,则应最终得到非常密集的处理程序代码,您显示的每个表都需要越来越复杂的处理程序方法。使用此技术的早期版本的代码。通过完全在处理程序中构建 DataTable,您可以省去 DataTable 在 JSF 中所占用的空间,最终得到用于许多 Eaz 相关的显示的非常紧凑且可维护的代码。
清单 5. ViewAuthZ.jsp JSF 页面
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%-- jsf:pagecode language="java" location="/src/pagecode/ViewAuthZ.java" --%>
<%-- /jsf:pagecode --%>
<%@page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<html>
<head>
<title>ViewAuthZ</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="theme/stylesheet.css"
title="Style">
<link rel="stylesheet" href="theme/eauthz.css" type="text/css">
</head>
<f:view>
<body>
<h:panelGrid styleClass="eazPanel">
<h:outputText styleClass="eazHeading" value="Actors in the ACLs and their allowed
actions"/>
</h:panelGrid>
<h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.actorTablePanel}"
/>
<h:panelGrid styleClass="eazPanel">
<h:outputText styleClass="eazHeading" value="Actions in the ACLs and their allowed
actors"/>
</h:panelGrid>
<h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.actionTablePanel}"
/>
<h:panelGrid styleClass="eazPanel">
<h:outputText styleClass="eazHeading" value="Specific Named Actors and their
Actions"/>
</h:panelGrid>
<h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.
permissionActorPanel}" />
<h:panelGrid styleClass="eazPanel">
<h:outputText styleClass="eazHeading" value="Actions in the ACLs for Specific
Named Actors"/>
</h:panelGrid>
<h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.
permissionActionPanel}" />
</body>
</f:view>
</html> |
外观配置和处理程序
由于您使用的是动态呈现,因此您的处理程序实现了几乎所有视图功能,让您能够在 JSF 页面中完整地显示 h:panelGroup 标记。由于我们一直在介绍 JSF 中的动态页面呈现,现在我们将详细地了解一下处理程序,而在此之前,我们首先来快速了解一下 JSF 配置。
清单 6 显示了 faces-config.xml,该文件位于您的 Web 应用程序的 WEB-INF 目录中。该文件负责配置您的 JSF 应用程序,将对您当前的显示执行以下三种功能:
- 定义托管 Bean 以表示授权管理器界面,指定实现类和将在此应用程序中用到的管理器的名称,如“authorizationManager”。应用程序范围意味着您的 authorizationManager 是独立的,应用程序中只有一个共享实例。
- 将您的处理程序定义为被应用程序识别为“authZviewHandler”,并通过 org.eaz.handlers.AuthZViewHandler 类实现的第二个托管 Bean。我们在前面提到过,“authZviewHandler”是用于将 h:panelGroup 标记与支持 Bean 绑定在一起的 JSF 页面的名称;托管 Bean 配置使这一绑定更有意义。
- 在您的处理程序上定义一种托管属性,将该属性指向使用 JSF 表达式语言 (EL) 表达式 #{authorizationManager} 的 authorizationManager Bean。此 EL 与您在 JSF 页面中所用的将 panelGroup 与处理程序绑定在一起的 EL 相同;在本例中,您指示 JSF 框架将 authorizationManager 的实例(有且仅有一个)插入到处理程序类中。对处理程序的范围有要求,因此将需要为每个请求创建一个新的处理程序。(这是依赖项注入的基本形式,更完整的形式以 Spring 这样的框架表示。)
清单 6. faces-config.xml 中的 JSF 配置
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<managed-bean>
<managed-bean-name>authorizationManager</managed-bean-name>
<managed-bean-class>org.eaz.svc.AuthorizationManagerImpl
</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>authZviewHandler</managed-bean-name>
<managed-bean-class>org.eaz.handlers.AuthZViewHandler
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>authorizationManager</property-name>
<value>#{authorizationManager}</value>
</managed-property>
</managed-bean>
</faces-config> |
AuthZViewHandler 类实现
构建 DataTable 的 PanelGroup 容器
我们首先使用公共 HtmlPanelGroup getActorTablePanel() 方法来检查处理程序。您还记得,这是在上面的 JSF 部分中用于对 <h:panelGroup styleClass="eazPanel" binding="#{authZviewHandler.actorTablePanel}" /> 标记进行绑定的方法。在解释方法名称时,JSF EL 提供了隐式 get 来调用此绑定。这一特定方法可以构建 DataTable 对象并基于参与者视图将其嵌入到 ACLPerspective 的配置调用的面板中。处理程序包含适用于 JSF 页面中的每个 <h:panelGroup... /> 绑定的一个类似方法,每个绑定标记从 ACLPerspective 的以不同的方式构造的实例中呈现一个表。
正如您在清单 7 中所看到的,这一过程从创建 HtmlPanelGroup 实例开始;HtmlPanelGroup 实例是实际绑定到 h:panelGroup 标记的对象,将在 HTML 表中呈现其中的所有内容。您的任务是从 ACLPerspective 中创建一个 DataTable,并在 HtmlPanelGroup 实例中插入此 DataTable。
将处理程序的依赖项注入引用用于您的 authorizationManager 服务层,以获取 aclHolder 实例的列表。我们已使用来自第 2 部分中的 JUnit 测试中的代码对 authorizationManager 进行了增强,使大量 aclHolder 对象和 MockPrincipal 对象可用于测试。(在生产中,您可能需要根据用户或应用程序定义的标准将您想要可视化的对象从数据库中提取。)
再次调用 authorizationManager 服务层,您将检索到您的一系列 testAclHolder 的 ACLPerspective,并调用该透视图的 getAclActorTable 方法以便检索您的表格信息。在本示例中,您并未在构造函数中提供命名用户的列表,因此返回的表将为配置表,而不是权限表。
您的方法配置了以下行中的 valueBindingDescriptor 字符串:最终的 String valueBindingDescriptor 是一个 JSF EL 表达式,指向用于实际填充 DataTable 的各行的方法;在本例中是当前类中的 getActorRows 方法(请记住 EL 方法绑定表达式中的隐式“get”)。JSF 生命周期实现将在适当的生命周期阶段中针对此方法执行回调,以检索表的行信息。我们使用自定义的专用 helper 方法将 EL 表达式字符串转换为实际 JSF valueBinding 实例。(JSF Version 1.1 将 ValueBinding 类用于此目的。在 JSF 1.2 和 Java EE 5 中,您将看到 ValueExpression 类实现了同一功能。)
最后,您需要调用自己的 constructDynamicDataTable 方法以及 actorTable 和回调绑定,以实现行获取。如您将在下面看到的,constructDynamicDataTable 能够构建一个动态 DataTable,仅使用这两条信息显示您的 actorTable,以便它能用作构建任何面向 ACLPerspective 的 DataTable 的实用方法。
将您新构造的 DataTable 添加到 HtmlPanelGroup 实例中作为一个子实例,然后将 HtmlPanelGroup 返回到 JSF 控制器。此过程类似于任何 ACLPerspective JSF 显示。
清单 7. getActorTablePanel() 方法用于构造针对 ACLPerspective 的 aclActorTable 的 DataTable
/*
* We bind to a panel instead of directly to the datatable because the
* DataTable component requires the page to define the iteration var and we
* wish to do so in the back end
*/
public HtmlPanelGroup getActorTablePanel() {
if (actorTablePanel == null) {
actorTablePanel = new HtmlPanelGroup();
ACLPerspective perspective = getAuthorizationManager()
.getACLPerspective(getTestACLHolders());
String[][] actorTable = perspective.getAclActorTable();
final String valueBindingDescriptor = "#{authZviewHandler.actorRows}";
UIData dataTable = constructDynamicDataTable(actorTable,
createValueBinding(valueBindingDescriptor));
actorTablePanel.getChildren().add(dataTable);
}
return actorTablePanel;
} |
构造动态 DataTable
清单 8 显示了 constructDynamicDataTable 方法,此方法减少了 DataTable 构造的复杂程度。您首先实例化 HtmlDataTable 和将其 ValueBinding 作为一个参数设置为一个提供的值。这有效地设置了表上的回调方法,因此 JSF 生命周期能够知道在需要获取表行时使用哪个方法。
接下来是一个易于出错的步骤,您将 DataTable 的 var 属性设置为一个字符串值,在本例中为“rowVar”。您将再次调用 DataTables 对各表行执行隐式循环,var 属性指示迭代过程中的当前行。对于您来说,这意味着在您创建用于显示表内容的绑定表达式时,需要使用在此表达式中为 var 配置的字符串,即“迭代过程中的当前行”。
DataTable 对象是以一次一列的方式构建的,每次填充一行。完全不确定的列数促使我们在处理程序中进行动态实现;行在本质上是动态的。但是,表的宽度(与列数相关联)可以看作 perspectiveTable 的任意行的“长度”,方便地从处理程序中获取,因此您可以使用 perspectiveTable[0].length 来设置您的列数。
然后您可以对现有的列执行循环,在此过程中创建和配置您的 DataTable 的列。首先,实例化 UIColumn 对象以表示列,然后以 HtmlOutputText 实例的形式为特定列创建标题单元对象。稍后您将发现,您希望使用标题,因为它们在 DataTables 中可以为独立的样式,让您能够很好地控制表在浏览器中的显示方式。我们将 outputText 实例的值设置为 perspectiveTable 的第 0 行单元(perspectiveTable 的第 0 行是显式的并且始终是标题行,可以支持此功能)的内容。最后,您在标题单元上设置样式,然后使用 UIColumn.setHeader() 方法将其添加到作为标题的列。
现在,可以将组件添加到 UIColumn 中以在除标题行之外的任何行中表示列的内容。再次,您可以将 HtmlOutputText 实例用于此目的——这正是您想替换输入或下拉列表对象来实现维护页面(而不是此显示页面)的原因所在。
DataTables 具有标题样式,但是在本质上并未为矩阵式样的表最左边的列提供样式功能,因此您可以通过手动进行测试以查看您是否处于第 0 列(最左边的列),然后相应地设置样式。
最后,您需要使用 JSF EL 将您的单元组件的值绑定到您在前面定义的 var 值。这一绑定是在行创建时执行的。绑定 ("#{rowVar[" + i + "]}")) 将被 JSF 解释为具有以下意义:
- 访问您要对其进行遍历的当前内容行,使用 rowVar 的当前值以查找它。
- 在该行中,查看用“i”指示的当前列编号然后使用在此行中找到的值作为此组件的值。
将已配置和值绑定的组件添加到当前列中,通过将完成的列添加到 DataTable 的子项中结束列循环迭代。
在构建了所有列之后,DataTable 实例即告完成,您可以将实例返回到将把它嵌套为一个子项的面板中。
清单 8. 从 perspectiveTable 构造动态 DataTable
/*
* Construct a dynamic data table based on the perspective table passed.
* Many thanks to BalusC for clarifying a very opaque interface!
*/
private UIData constructDynamicDataTable(String[][] perspectiveTable,
ValueBinding vb) {
HtmlDataTable dynamicDataTable = new HtmlDataTable();
// Bind the actual data
dynamicDataTable.setValueBinding("value", vb);
/*
* Create a var for the datatable's rows. Must reference this var in the
* component binding expression so that the components draw their values
* from the correct row of the datatable
*/
dynamicDataTable.setVar("rowVar");
// Get number of columns from the perspective
int columns = perspectiveTable[0].length;
// Loop through columns.
for (int i = 0; i < columns; i++) {
/*
* Create UI column for the dataTable. Note that JSF 1.2 changes
* this interface and uses an HtmlColumn
*/
UIColumn column = new UIColumn();
/*
* Create header. This gives us control over header styling but
* requires us to strip the header row from our table of values so
* it doesn't appear twice
*/
HtmlOutputText header = new HtmlOutputText();
header.setValue(perspectiveTable[0][i]);
header.setStyleClass("eazTableHead");
column.setHeader(header);
// Create component for cell contents and add to column.
HtmlOutputText cellComponent = new HtmlOutputText();
// Style the first (aclHolder) column differently
if (i == 0) {
cellComponent.setStyleClass("eazTableHead");
} else {
cellComponent.setStyleClass("eazColumnClass");
}
/*
* Value binding must match the dataTable's var, in this case
* "rowVar"
*/
cellComponent.setValueBinding("value",
createValueBinding("#{rowVar[" + i + "]}"));
column.getChildren().add(cellComponent);
// Add column to datatable.
dynamicDataTable.getChildren().add(column);
}
return dynamicDataTable;
} |
填写 DataTable 行内容
通过查看用于为 DataTable 构建行内容的方法之一,完成您对处理程序的探索。此方法是在 DataTable 的 valueBinding 中配置的,必需引用用于构建 DataTable 本身的相同风格的透视图表(参与者或操作,配置或权限),否则,标题和内容将不匹配。保持对参与者的配置显示的关注,现在我们将详细了解一下清单 9 中的公共 List getActorRows() 方法,该方法可以被 EL 方法绑定表达式“#{authZviewHandler.actorRows}”引用。
到目前为止,您应已对 ACLPerspective 的使用有所了解。要记住的重要的一点是,针对行检索到的透视图必须与检索到的用于构建 DataTable 及其列的透视图相同。专用 List getRows(String[][] perspectiveTable) 方法(如清单 9 中所示)可对透视图表的顶行进行条带化,因为您在构建 DataTable 时已使用顶行来配置标题。
清单 9. AuthorizationManagerImpl 中的服务层保护
/*
* Returns a list of rows from the perspective relating aclHolders in rows
* to actors specified in the ACLs in columns
*/
public List getActorRows() {
ACLPerspective perspective = getAuthorizationManager()
.getACLPerspective(getTestACLHolders());
String[][] actorTable = perspective.getAclActorTable();
// return actor rows;
return getRows(actorTable);
}
/*
* Private utility method to strip the headers from a perspective table and
* convert it to a list
*/
private List getRows(String[][] perspectiveTable) {
List rowList = new ArrayList();
for (int i = 1; i < perspectiveTable.length; i++) {
rowList.add(perspectiveTable[i]);
}
return rowList;
} |
显示结果
图 6 显示了本示例中的 JSF 网页的实际显示,以及 ACLPerspective 的每个变体的呈现和样式表。
图 6. ACLPerspective 的变体的 JSF 显示
结束语
本文阐述了将应用程序操作、组和参与者,以及它们与 EAz 权限的关系在图形界面中实现可视化的技术。此方法提供了对应用程序的授权策略的易于理解的观点,可以扩展以构造用于进行授权管理的管理控制台界面。
开发人员可以使用总体 EAz 框架及其技术实现针对任何 Java 应用程序的可靠、灵活且可扩展的授权机制。EAz 增强了(但是并未取代)现有的 Java EE 授权机制,按授权、按异常进行管理,可用于命名用户或简单的组这样简单的情形,也可用于基于多种组成员关系、继承访问控制列表的授权这样复杂的情形,以及区分不同的组织成员关系的访问控制。
本系列的其他文章
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| COde sample | EAZArticle3Final.zip | 41 KB | HTTP |
|---|
参考资料 学习
讨论
关于作者
对本文的评价
|