结合使用基于 Enum 的访问控制列表和 EAz 进行细粒度 Java EE 授权,第 1 部分: 问题空间和 EAz 体系结构

随着 Java™ 5 ENumSet 和基于 Enum 的授权 (EAz) 的出现,现在能够实现有效的、易于维护的 Java 访问控制列表框架,可以对应用程序资源进行细粒度控制。 本文来自于 IBM WebSphere Developer Technical Journal

Robert Patt-Corner (rpatt-corner@proqual.net), 首席技术官, Proqual-IT

Robert Patt-Corner 是 ProQual-IT 的首席技术官,主要研究面向服务的体系结构。之前,他是 Noblis(以前称为Mitretek Healthcare)的首席技术官,领导团队开发了基于 Web 的自动化卫生保键警报服务。Robert 获得的认证包括 WebSphere 管理、Rational 应用程序开发和 Lotus 管理和开发。您可以通过 rpc@dorsetwest.comrpatt-corner@proqual.net 与 Robert 联系,并且可以访问他的网站,网址是:www.dorsetwest.comwww.proqual.net



2008 年 1 月 28 日

引言

尝试对访问应用程序资源进行细粒度控制的 Java 开发人员很快会受到内置 Java Platform Enterprise Edition (Java EE) 声明式授权的限制。开发人员通常尝试各种解决方案,其中包括 Java EE 编程式授权、外部框架(如 Acegi)、各种对 Java EE 内置声明式授权模型的特殊扩展和二进制访问控制列表 (ACL) 的实现。

通常,外部框架缺少足够的能力或严重依赖内置的 Java EE 授权和身份验证模型,迫使开发人员丢弃 Java EE 大量有用的本机功能。到目前为止,为了在较大范围的实现中获得更大的效率,自主开发的 ACL 框架仍需要通过复杂的、存在潜在脆弱性或难于维护的二进制算法实现。

Java 5 引入了 Enum、EnumSet 和 EnumMap 实体,正式作为对一个完全不同问题的解决方案:处理一组绑定离散值的方法。在此过程中,Java 5 的开发人员实现了 Enum 和 EnumSet,并将其映射为有效的二进制实体。这样,开发人员仅使用 Enum 及其扩展就可以非常容易地构造易于理解和维护的传统 ACL 实现。

基于 Enum 的授权(Eaz,读作“easy”)系列介绍如何在 Java 5 中执行完整的 ACL 实现,其中包括离散的二进制权限、任意命名的权限集、对受保护的资源应用和快速测试访问控制的方法,以及使用 IBM® WebSphere® Application Server V6.1 和 IBM Rational® Application Developer 7 的 JavaServer™ Faces (JSF) 功能显示特定访问控制的真值表的方法。

本文是三篇文章中的第一篇,将介绍 EAz 解决方案的要求和整个体系结构,其中包括授权检查的接口、识别上下文的访问控制机制和基于 Enum 的配置类。下一篇文章将介绍每个类的代码和实现详细信息,最后一篇文章将介绍在基于 Web 的 JSF 应用程序中与现有 Java EE 授权的显示集成,方法是对使用 Web 应用程序的不同问题实施一个 JSF 解决方案,以图形方式表示哪些离散权限可用于 Web 框架中的用户和组。


Java EE 授权:哪些在标准中可用

Java EE 规范提供了强大的声明式和编程式授权功能(请参阅参考资料)。我们首先详细介绍 Java EE 授权,以了解 Java EE 5 容器的内置功能,然后分析几个应用程序区域,其中授权需要标准中功能之外的功能。

Java EE 授权结构管理安全应用程序资源之间的关系,如页面和方法、应用程序特定的 Java EE 角色和组成员关系。图 1 列出了适当简化的重要关系。

图 1. Java EE 5 授权结构
图 1. Java EE 5 授权结构

从底部开始:

  1. 通过部署描述符中的安全约束,可以将应用程序资源(如 EJB 方法和 Web 页)声明为需要授权才能进行访问。(代码中使用的角色和部署中公开的角色之间有一个间接层,但是它与本文关系不大。)将安全约束映射到满足描述符本身中的授权所需的 Java EE 角色。

  2. 尽管 Java EE 规范没有指定映射的方式,角色关系通常在部署时从某些用户注册表映射到组或用户。可以将一个角色授予多个组或用户(主体);一个组或主体可以具有映射到它的多个角色。用户主体还依靠它们所属的组获取角色映射。角色到组或主体映射的灵活性是 Java EE 授权框架的强项。

  3. 当主体尝试访问安全约束保护的资源时,Java EE 安全实现每次检查一个映射的角色,直到可用的角色授予对资源的访问,或用尽所有角色。允许访问的第一个角色返回成功;如果不允许任何角色访问,则 Java EE 授权返回失败。

将声明式授权移动到 Java EE 编程式授权

声明式授权完全依赖于授权策略的部署描述符约束。描述符(如 Web 应用程序的 web.xml 文件或 EJB 组件的 application.xml)完全定义声明保护的资源和授权访问这些资源的角色。

组仅在实际部署应用程序时起作用;运行该应用程序的一个站点可能将一组组和命名的用户映射到一个角色,另一个站点可能映射完全不同的另一组组和用户。

声明式授权的先行映射方便了管理员理解、维护和修改安全策略,但即使对于很简单的场景也过于刻板,例如,一个角色保护的代码片段包含需要其他角色才能授权的其他片段。当然可以让代码适应这种缺少精细控制的情况,但是声明式授权的刻板性最终会扭曲和复杂化应用程序的结构。

Java EE 编程式授权通过在代码中引入动态授权元素,从而在一定程度上缓解了此问题。编程式授权对角色、受保护的资源、组和命名用户使用与 Java EE 声明性安全相同的结构,但是支持编程人员在运行时以应用程序特定的方式检查访问。Java EE 编程式授权通过四个安全识别方法调用(其中两个在 Web 应用程序中使用,两个在 EJB 中使用)直接从应用程序代码调用:

  • isCallerInRole (EJBContext)
  • getCallerPrincipal (EJBContext)
  • isUserInRole (HttpServletRequest)
  • getUserPrincipal (HttpServletRequest)

当允许多个角色访问以声明方式保护的资源(如页面或方法)时,您通常会使用编程式授权,但是应该只允许角色的一个子集访问功能的一个特定子集。


Java EE 授权的限制

通过上述强大的 Java EE 授权功能,许多开发人员可以很好地进行管理,而无需其他授权功能。不过,Java EE 授权中仍存在一些明显缺陷,这些缺陷将需要 Java EE 安全规范之外的功能。

细粒度权限分配

我们使用权限一词来表示我们在特定应用程序中使用的最小单元或最细粒度的授权;例如,能够在复杂工作流中分配单个阶段或者读取或编辑单个字段或字段组。我们将使用资源一词来表示可以使用权限保护的最细粒度的功能;例如,Web 页或 EJB 方法调用。

Java EE 的最小单元的权限是 Java EE 角色。必须在每个 Web 或企业应用程序的部署描述符中定义每个角色,并且必须创建安全约束来保护每个授权控制的资源。此外,管理员(或应用程序部署人员)在部署时必须向适当的组和用户分配每个角色,并且在组和用户随着时间的推移而更改时维护映射。

Java EE 的最小单元的授权资源是方法或 URI(对于声明式授权)者代码块(对于编程式授权)。问题是 Java EE 角色或对代码块的特别编程测试并不适合细粒度的权限控制。从理论上讲,将 Java EE 用于细粒度的权限不存在任何问题。应用程序开发人员必须为应用程序中的每个权限定义 Java EE 角色,并且在整个应用程序的生命周期中维护部署描述符中的角色,和/或在代码块中测试应用程序特定的角色成员关系。同样,部署人员必须在整个应用程序生命周期中维护个别权限到组的或主体映射。

随着整个企业应用程序的发展,Web 应用程序会越来越多,EJB 也会越来越多(其中每个 EJB 都需要一个部署描述和映射),所以维护工作和错误的机会将增加。授权成为一堆 XML 声明和程序代码;分析人员难以理解,它们成为重新配置和部署的障碍。(Java EE 5 还允许通过注释在代码中声明安全约束,但是此功能排除了声明的中心存储库,在某种程度上使情况更为复杂。)

因此,开发人员几乎总是使用较粗的粒度将 Java EE 角色映射到一组绑定的相关权限。Web 应用程序通常定义单个角色,以授予访问表示为 URI 模式的一组绑定的 Web 页的权限。例如,清单 1 中的安全约束保护以“/reports/”开头的所有 URL 和称为 reportEnabled 的单个角色,并在部署时可以将其映射到多个组。

清单 1. 映射到单个角色的多个典型 URI
<security-constraint>
<display-name>View Reports</display-name>
<web-resource-collection>
	<web-resource-name>All Reports</web-resource-name>
	<url-pattern>/reports/*</url-pattern>
	<http-method>GET</http-method>
	<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
	<role-name>reportEnabled</role-name>
</auth-constraint>
</security-constraint>

如果您需要根据不同角色使用不同的权限来运行不同的报告组,则声明式安全可能仍可以维持,但是,如果应用程序需要细粒度访问个别报告,则对于需要保护的每个报告元素,您需要定义和维护这些安全约束之一。Java EE 的声明式授权方法会随着复杂性的增加而崩溃,尤其是,随着时间的推移需要开发人员和部署人员维护应用程序。

提高控制粒度的 Java EE 编程式授权

Java EE 编程式授权可以提高 Java EE 提供的声明式授权的控制粒度。根据本文的报告示例,可以让 reportEnabled Java EE 角色中的所有用户访问报告页或方法,然后根据编程角色测试的第二个角色授权或取消授权报告的某些方面。清单 2 显示了 reportEnabled 角色保护的方法如何为具有 extraReports 角色的主体打印一些额外报告:

清单 2. 使用 Java EE 编程式安全扩展报告
printUsualReports();	
    if (ctx.isCallerInRole("extraReports")) {
      printMoreReports();
    }

为角色成员关系编程手动编码的测试可以明显为授权功能增加一些灵活性和粒度。

编程式授权的限制

通过Java EE编程式授权获得的灵活性有一定的范围限制并且成本特别高。开发人员可以根据需要授权或拒绝任意功能,但是应用程序授权策略现在部分或全部嵌入在其代码中。分析人员需要了解最初的声明式授权、通过编程形式授权的扩展和二者的交互。了解和维护授权策略更加困难,即使代码没有任何缺陷。详细地描述和控制位置的开发技巧以及使用编程式授权的方法有助于控制这种复杂性。不过,复杂性问题和需要后续的详细结构不是 Java EE 授权所独有的;这是编程式授权的一般特征。

更大的问题是 Java EE 编程式授权不提供与其带来的复杂性相应的好处。Java EE 编程式授权完全依赖于与声明式授权相同的部署角色,并存在相同的粒度问题——Java EE 角色可以很好地映射到权限组,但很难映射到个别权限。Java EE 角色的另一个问题是其本质上的静态结构,并且在运行时不适用于程序特定的上下文或关系。

另一方面,Java EE 角色的静态特性对于可维护性是一件好事。仅使用 Java EE 授权的应用程序完全具有其角色、身份验证约束和编程式授权调用定义的授权策略。但是,这也意味着给定的 Java EE 角色可以为与其关联的所有个体维护相同的权限集,无论外部关联如何。应用程序无法使用单个“报告阅读者”角色授权一个组织单元中的用户访问报告 A、B 和 C,另一个组织单元中的用户访问报告 A、D 和 F,即使在这两种情况中角色的业务意义完全一样。

实例和类授权

大多数授权系统(包括 Java EE 授权)可以保护对整个资源类的访问;例如,一组匹配一个模式的 Web URL 和一个接口上的所有方法。基于实例的授权更为困难。例如,在 Java EE 中,您可以容易地表达是否可以调用特定 EJB 上的特定方法,但是您无法表达特定部分是否可以使用该 EJB 的特定实例。例如,假设帐户 EJB 具有 transferFunds() 方法。使用典型的 Java EE 声明式授权无法表达内容为“您只能从特定的帐户转帐资金”这一约束,除非您为每个帐户创建一个唯一的 EJB 类型。显然,这是不可取的。需要授权的实例通常不是 Java EE 构件,而是特定于应用程序的域对象实例,如个别银行帐户。

您需要的是比较简单的、更具表达力的强大授权模型。


Java EE 授权之外的资源

现在我们介绍一个示例应用程序,来演示一个很容易理解的授权问题,解决该问题需要的资源比 Java EE 提供的要多。假设“虚拟校园”应用程序具有多个级别的访问权限:

  1. 整个校园及其公共区域。
  2. 各种大楼和校园大楼中的开放会议空间。
  3. 大楼中的个人办公室。
  4. 大楼中特殊的保护空间。

此类应用程序的授权模型非常类似于一组物理大楼和类似特征的房间——事实上,您可以通过添加访问卡和传感器将授权模型应用到实际校园。

下面是此虚拟校园应用程序的授权模型:

  1. 对给定的校园具有登录级别授权的用户可以进入校园,并在校园的公共空间执行一些缺省操作。
  2. 对特定的大楼具有登录级别授权的用户可以进入特定的大楼和该大楼中公共会议空间。
  3. 对特定的个人办公室具有登录授权的用户可以进入该办公室。
  4. 具有进入专门保护空间(例如,人工智能实验室)授权的用户可以进入该空间。

示例授权模型非常简单,它只定义单个权限,即登录权限。即使这样,仅使用 Java EE 声明式和编程式授权以可维护方式实现此模型也非常困难。即使向该模型再添加一个权限(例如,授予其他人进入空间的权限),则对于 Java EE 授权,该模型也极其困难。

我们将使用此示例的要求作为概念基准来探索授权功能。以此示例模型为基础的常见功能有:

  1. 管理权限和权限组比管理 Java EE 角色更精细,并且管理员可以容易而又安全地配置、了解和维护。
  2. 引入授权资源访问的方法,使之可以直接受应用程序的域模型驱动——特别是通过授权才能进入校园、大楼、房间等。
  3. 扩展并管理数千个校园、高达 100,000 个命名用户和数百个权限。
  4. 与普通 Java EE 声明式和编程式安全共存,并对其进行扩展,这样,现有授权策略可以根据需要继续管理其他地方。

使用纯 Java EE 声明式和编程式授权为虚拟校园示例提供授权并不切合实际;代码会快速地变为许多不可维护的 IF-THEN 块和标识检查。

Acegi 解决方案

我们发现,对现有框架进行细粒度、可配置的 Java EE 授权的最好方法是结合使用 Spring(请参阅参考资料)和 Acegi;Spring 提供将授权作为一个方面对待的能力,根据需要将安全授权注入方法,Acegi 提供实际的身份验证解决方案。按其创建者的意思,Acegi 一词是指:

一种用于企业软件的功能强大的、灵活的安全解决方案,特别适用于使用 Spring 的应用程序。使用 Acegi 安全性可以为应用程序提供全面的身份验证、授权、基于实例的访问控制、通道安全和人工用户检测功能。

其文档中是这样写的,并通过一个小实验说明 Acegi 完全符合其声明的功能。对于一般需求而言,它似乎是一个可行的解决方案,但是有如下两个明显的例外情况:

  • Acegi 没有真正地插入或增强 Java EE 授权;它只是替换Java EE 授权和 Java EE 身份验证。为了维护现有的标准功能,我们认为完全回避基于 Java EE 的安全机制是不明智的,尤其是与面临的授权问题无关的身份验证机制。

  • Acegi 至少在我们检查它时存在某些可伸缩性限制。各种权限都基于屏蔽一个 32 位整数——示例应用程序和要求至少需要 64 位才能处理所需的原子权限。

第一个问题(即完全替换 Java EE 授权和身份验证)是其真正的主要症结。在应用程序中,授权和身份验证是基础机制,对于自定义实现,丢弃二者是冒险的选择。第一个常见的风险是需要为第三方框架提供连续的支持和开发。但更重要的是,这样下去的代价是失去增强的机会。失去机会不仅限于身份验证和授权中的 Java EE 更改;替换 Java EE 安全可能与 Java EE 和以 Java EE 安全为基础的供应商扩展增强发生冲突。

自定义解决方案

开发自定义授权解决方案的主要风险是不可避免地创建大量不可维护的重复安全代码,结果比原始问题更糟。例如,可以很容易地增加本质上相同的角色,或在代码中将 Java EE 编程式授权测试与自定义测试混合,以便在特定情形中获得所需的授权结果,但是这样做很快会无法理解和管理所有情况的组合影响。

好的做法应有助于降低风险;例如,将安全视为一个方面并从应用程序代码隔离授权决策。好的的设计可以确保自定义授权的行为如同一个“黑匣子”,当需要进行决策,并且这种决策超出 Java EE 编程式授权模型的能力时,可以从应用程序代码调用不透明的 API。将扩展授权设计为一个接口也可以在实现中提供某种程度的独立性。完善必要条件可以降低自定义授权解决方案中涉及的风险。具体来说,自定义解决方案必须:

  1. 作为一个简单的接口处理,使其适用于应用程序的横向方面。
  2. 保持现有 Java EE 声明式和编程式授权处于完好无缺和可操作状态。
  3. 与身份验证无关,并适合于标准 Java EE 身份验证和自定义身份验证。
  4. 能够授权访问应用程序的域对象,而不仅是 Java EE 构件。
  5. 可扩展、易于维护,尤其是速度非常快。频繁地调用授权,并且可以跨大量的代码进行调用。授权中的任何瓶颈都有可能对系统性能造成严重而广泛的影响。
  6. 通常情况下足以适用于许多应用程序域。

访问控制列表和权限集

存在这样一个区域,其中细粒度授权模型是普遍存在的和众所周知的:即操作系统。几乎每个现代操作系统都使用基于位屏蔽权限的模型,该模型基于主体和组按主体处理资源控制。在某种意义上说,Acegi 是将像操作系统一样的访问控制列表应用到 Java EE 环境的一种尝试。

访问控制列表 (ACL) 是具有所有者和索引列表或散列的对象,如图 2 所示。

图 2. 访问控制列表的基本结构
图 2. 访问控制列表的基本结构

访问控制列表是权限列表或保护资源的权限集。ACL 的权限列表中的每个条目将资源上的权限集授予一个主体或组。几乎所有现代操作系统都使用 ACL 授权对资源的访问。图 3 显示了 Linux® 操作系统管理的文件中 ACL 的图形显示:

  • ACL 资源和所有者是称为 dekihost 的文件。
  • ACL 的列表包含三个权限集:
    1. 主体“rpc”索引第一个条目,并授予读写权限。
    2. 组“admin”索引第二个条目,并授予组的所有成员相同的读写权限。
    3. 名为“Others”的虚拟 Linux 操作系统组索引第三个条目。不属于 rpc 或 admin 组成员的任何主体都被视为“Others”的成员,并且仅授予单一的读取权限。
图 3. 基于 Linux 文件的 ACL 的图形表示形式
图 3. 基于 Linux 文件的 ACL 的图形表示形式

详细讨论 ACL 非常困难。一个原因是 ACL 保护的资源中的语言主要与 ACL 本身关联。在本文件示例中,文件的所有者是 rpc 主体。文件所属关系是 Linux 操作系统结构。不过,ACL 的所有者不是 rpc,而是文件本身 dekihost,即 ACL 保护的资源。考虑到本文的目的,“ACL 的所有者”特指 ACL 保护的资源。

ACL 的范围

ACL 将权限集授予主体或组——但是,权限的目标和范围是什么?ACL 的范围至少是 ACL 所有者。如果 rpc 具有写入权限(由图 5 中的 ACL 列出),则它是可以写入的 dekihost ACL 所有者。

但是范围不一定仅包含 ACL 所有者。甚至在文件系统的相对简单的标准情况中,ACL 范围包含某些有趣的有用含意。例如,用于文件夹的 ACL 几乎与用于文件的相同。不过,大多数操作系统实现一个与文件夹相关的规则来控制创建新文件的 ACL 的方式。

图 4 显示了一个文件夹 ACL,它向 rpc 主体提供创建文件和删除文件权限,还提供一个附加权限:读取文件夹中的文件的能力。这意味着 rpc 创建了文件后,将无法再修改它;ACL 对文件夹实现写入一次的规则。本示例需要注意两个重点:

  • 从 ACL 角度看,rpc 权限集中有四个个别权限,但不像 GUI 所示的那样,一个权限集包含两个文件夹权限,另一个权限集包含一个文件权限。Linux GUI 将文件系统概念与 ACL 概念合并,从而取消两个图形部件中的文件夹权限和文件访问权限,但是 ACL 的基础结构将授予 rpc 四个权限:列出文件、创建文件、删除文件和读取文件。

  • ACL 不了解文件夹和文件之间的包含关系;ACL 仅列出谁在其范围中执行什么任务。包含关系和范围由操作系统实现。最后一点对应用程序中的 ACL 特别重要;它意味着 ACL 不仅保护其资源,而且还可以按应用程序特定的方式保护与其资源相关的其他对象。

图 4. ACL 权限集
图 4. ACL 权限集

总之,ACL 的范围至少是它保护的资源,不过,应用程序可以扩展该范围,以实现设计人员所需的授权模型。

虚拟校园说明

下面我们还用虚拟校园应用程序示例说明 ACL 范围。我们还记得,授权规则之一声明了授权进入校园特定大楼的用户也被授权进入该大楼中的任何公共空间。您可以对每个授权控制的大楼使用单个 ACL 实现此规则,并授予 ACL 中的用户和组“进入”权限。然后可以将大楼的 ACL 范围扩展,以便在应用程序中包括与大楼关联的所有公共空间。您可以非常有效地扩展大楼的 ACL 范围,而无需维护大楼中每个公共空间的 ACL。让我们假设您编写与以下所示类似的授权服务接口:

public boolean permit(Object permission, Object doorway);

在虚拟校园域模型中,您可以将每个门口与一个大楼关联。在 permit() 实现中,您可以查找相关门口的 ACL,在本示例中,指大楼中公共空间的门口。公共门 ACL 不存在,因此,您需要后退到上一级 ACL。您可以递归地执行此层次结构遍历;即继续查找上一级 ACL,直到发现 ACL。找到 ACL 后,检查当前用户的身份和组,并查看它们是否在 ACL 中列出以及列表是否具有“进入”权限。如果发现,授予授权,否则拒绝。

注意,此示例的强大功能是:ACL 查找的递归特性使您能够使用单个 ACL 定义范围广泛的授权区域,并在清晰的层次结构的任何级别定义例外。例如,您可以让公共区域包含个人区域;仅具有大楼“进入”权限的用户可以成功访问基于大楼 ACL 的公共区域,但不能访问个人区域,因为个人区域将使用自己的具有更多限制的 ACL 覆盖上一级 ACL。

第一步

原始 ACL 实现方法采用了与 Acegi 大致类似的途径:我们考虑使用 bitmask 的长变量集合(而不是整数)实现比 Acegi 允许的更多权限。位操作方法不存在任何一个丧失资格的问题,但是它在一些领域中确实面临着难题:

  • 按位逻辑对于普通程序员和分析人员非常复杂。因此,执行低级 ACL 实现的代码需要非常仔细和复杂的测试以及与应用程序程序员的较好隔离。

  • 位图的存储和检索将变得非常复杂,并且在使用可维护的方法快速地存储和检索位图时可能需要引入供应商依赖的数据库。

切换到基于 Enum 的授权

当继续进行自定义授权时,我们将独立使用 Java 5 的标准 Enum。Enum 是逻辑概念,与特定的语言无关,并表示命名的常数值,通常作为对离散集合中成员的枚举。Java 示例使用扑克牌类作为主要说明,该类为每个 Suit 和 Rank 创建 Enum,并且每张牌从 Suit Enum 中接收一个离散的常量命名值和一个 Rank Enum:

清单 3. Java 5 Enum 示例摘录
public class Card {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }
    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

    private final Rank rank;
    private final Suit suit;
    private Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    } ...

Java 5 为 Enum 提供多个集合类,并且在 EnumSet javadoc 中出现以下句子:

Enum 集合内部表现为位向量。此表示法非常紧凑和有效。此类的空间和时间性能应足够好,允许将其用作基于传统 int 的“位标志”的类型安全的高质量备选方法。如果指定的集合也是 Enum 集,则甚至批量操作(如 containsAll 和 retainAll)也应非常快地运行。

JavaDoc 说明了 Java 5 的实现者十分明智地创建了可以容易地表示离散权限集合的 EnumSet,并使用我们原计划需要手动构造和测试的高性能位向量格式实现它。

我们放弃了权限集位图的手工实现,而使用 EnumSet。事实证明,使用 Enum 和 EnumSet 代替自定义位向量来创建 EAz 非常简单。在本文的其余部分中,将讨论总体的概念、类、服务接口和动态行为,然后在下一部分中详细说明实现。


Eaz 简介

EAz 是 Java EE 授权的基于 Enum 的授权扩展。EAz 依赖 Java 5 中 Enum 集合的位图向量实现和其他实现来执行一个基于 ACL 的简单方法,以便在标准 Java EE 声明式和编程式访问不能满足应用程序的业务需求时来保护 Java EE 资源。EAz 需要 Java 5 或更高版本才能运行,因为它依赖于 Java 5 Enum 类。

EAz 的操作概念

Eaz 的主要任务是定义和实现一个接口,以便任何应用程序的代码可以查询并授权对应用程序资源的访问。

EAz 需要一个应用程序实现 ACLManager 接口,该应用程序负责从持久数据存储库或缓存检索所需的保护资源的 ACL。应使用普通数据库类型(如 varchar 或字符串)保留和检索 ACL。

EAz 将 ACL 实现为 ACL 类,并使用 permit() 方法检查授权。EAz 示例代码可用来显示数据库字符串格式和 EAz Enum 格式之间如何相互转换。

EAz 将权限集建模为一个 PermissionSet 接口,即个别二进制权限的任意集合。在演示实现中,成功的授权需要 PermissionSet 中的所有权限,并且没有提供拒绝类型的权限。

带有静态初始化器的 Permissions 类为声明和配置可用的应用程序权限提供单一位置。初始化器组装 AllPermissions EnumSet。AllPermissions 包含在使用 EAz 的应用程序中可用的每个权限。还为 AllActions EnumSet 提供 Permissions 类,而 AllActions EnumSet 提供应用程序特定的操作到权限集的映射。它使 EAz 能够通过操作名称向应用程序的服务层提供“众所周知”的权限集。

实现 EAz 的应用程序通常使用 Permissions、ACLManager 和 ACL,如下所示:

  1. 希望访问资源的应用程序服务将资源标识符用作密钥,从 ACLManager 获取资源的 ACL。
  2. 应用程序服务检索它希望从 Permissions AllActions EnumSet 授权的操作,并使用静态 Permissions.getPermissionSetForAction(theAction) 方法检索在应用程序中执行操作所需的权限集。
  3. 应用程序调用 ACL 的 permit() 方法确定授权,并以组或用户标识符的形式传入标识信息。还可以将 Subject 用作组和标识信息的缺省源。在本示例中,我们使用 WebSphere Application Server APIs 获取组信息,但是,该概念是通用概念,应适用于任何应用服务器。
  4. ACL.permit() 方法在成功时以静默方式返回,或在失败时抛出 PermissionViolationException。

图 5 显示了典型的 typical EAz 授权流程的序列关系图。

图 5. 典型的 EAz 授权顺序
图 5. 典型的 EAz 授权顺序

最后,EAz 为包装典型的交互详细信息提供一个方便的 AuthorizationManager 接口,并为应用程序的服务层提供一个更简单的接口。

ACLManager

图 6 显示了 Eaz 的 ACLManager 接口,它由单个公共 getACL() 方法组成。

图 6. ACLManager 接口
图 6. ACLManager 接口

使用 EAz 的应用程序必须实现 ACLManager,并从缓存或持久存储中检索保护资源的 ACL。您可能记得,检索和保持 ACL 位图是考虑到初始的维护和数据库可移植性。EAz 示例实现演示了如何从简单的字符串保持和重新构建基于 Enum 的访问控制列表,并在检索 ACL 后保留位图的所有性能优势。在生产实现中,我们建议在分布式缓存中缓存检索的 ACL,以便在检索中避免任何性能损失;如果使用 Object-Relational Mapping (ORM) 框架(如 Hibernate)进行持久存储访问,则可以通过配置做到这一点。

图 7 显示了示例数据访问对象(Data Access Object,DAO)代码的关键部分,并演示了如何从属性文件中存储的字符串检索和重新构建 ACL 信息。我们使用基于属性的示例简化可下载的 EAz 代码;您需要在自己的应用程序中实现更适当的数据库表持久性。

图 7. 代段片段说明了如何从保持的字符串重新构建 ACL 信息
图 7. 代段片段说明了如何从保持的字符串重新构建 ACL 信息

EAz 持久性技术的本质内容在第 4 部分中,其中 Enum valueOf() 方法将字符串转换为其 Enum 表示形式。

图 8 显示了 ACL 接口。ACL 拥有组对资源的权限。其主要动态函数是 permit(...) 方法,该方法根据应用程序请求的权限对提供给 ACL 的组是否可用来确定授权成功或失败。

图 8. ACL 接口
图 8. ACL 接口

EAz 内部结构和配置

在深入研究本系列文章中下一篇文章中的代码之前,让我们先分析一下 PermissionSet 接口的结构和配置类 Permissions。了解 PermissionSet 和 Permissions 有助于我们了解支持 EAz 实现的 Enum 结构。

图 9. PermissionSet 接口和 Permissions 配置类
图 9. PermissionSet 接口和 Permissions 配置类

PermissionSet 接口

PermissionSet 接口(图 9)是各个权限的集合。该接口定义向 PermissionSet 的实例添加权限的各种方法,并查询该集合是否包含单一个体权限或作为子集的另一个 PermissionSet。

注意,addPermission 和 hasPermission 方法中引用的权限是在 Permissions 配置类中维护的静态 AllPermissions Enum 类型的成员。我们将 AllActions 和 AllPermissions Enum 作为 Permissions 配置类讨论的一部分来讨论。

Permissions 配置类

Permissions 配置类可用作在特定的应用程序中使用 Eaz 配置权限的静态 Enum 类型的容器。Permissions 从不实例化,它提供构建 Enum 类型的静态初始化器,该 Enum 类型可以为应用程序配置权限。

正如图 9 所示,Permissions 可创建两个静态 Enum 类型:一个称为 AllActions,另一个称为 AllPermissions。AllActions 是公共的,它定义可以在应用程序中授权的操作。AllActions 中的 Enum 常量相当于配置的应用程序命令的名称。可以从代码中的任何位置引用 AllActions 的 Enum 常量,例如:Permissions.AllActions.ACTION_WHATEVER。事先声明应用程序的授权操作可以对 EAz 授权的应用程序的结构进行测量,并提高其可维护性。

AllPermissions 是另一个静态 Enum 类型,但是与 AllActions 不同,它是 org.eazacl.acl 包中保护的内部成员,它在 EAz 服务层或应用程序层不可视。

AllPermissions 中的 Enum 常量表示实际控制授权的一元权限。将这些权限称为应用程序的“权限”。

Permissions 类构建静态映射,将每个操作与主体用来访问操作所需的 PermissionSet 相关联。这样,AllActions 中的操作 Enum 常量成为应用程序命令的名称和用来查找 PermissionSets(PermissionManager 用它来授权命令)的 Enum 常量。

EAz 应用程序调用公共静态方法 Permissions.getPermissionSetForAction(),以检索 AllActions 中任何操作所需的 PermissionSet。

AuthorizationManager 接口

图 10 演示了 AuthorizationManager 接口。AuthorizationManager 是简化应用程序访问 EAz 授权的 ACLManager 和 ACL 的简单包装。不是手动查询保护资源的 ACL 的 ACLManager,然后查询授权的 ACL,而是应用程序仅使用 AuthorizationManager 的 authorize() 方法处理详细信息。

AuthorizationManager 还使用 canAuthorize() 方法提供查询授权服务的功能,以确定给定的授权请求是否成功(如果进行了请求)。此功能在构造显示和操作授权实现的详细信息的用户接口时非常有用。

图 10. AuthorizationManager 接口
图 10. AuthorizationManager 接口

标识、组和 EAz

EAz 实现示例中存在某些限制,主要是清晰性和简单性。该实现仅处理单个组,即使该接口是为多个组设置的;忽略了在每个提供的组上进行迭代直到成功或用尽每个组这一详细信息。

初始的 EAz 实现仅为组提供授权,而不为命名个体提供授权。可以使用与组完全相同的方法实现个体授权。应用程序以不同的方式实现个体和组的命名;实现对个别用户至关重要的包含 permissionSets 的 ACL 只需要用一个约定或命名空间来区分个体和组。

在虚拟校园中使用 EAz

本文最后一部分向您介绍如何将 EAz 应用到虚拟校园示例。假设校园实现如图 11 所示。

图 11 示例虚拟校园布局
图 11 示例虚拟校园布局

该图列举了校园 A 和校园 B。我们重点说明校园 A,但是介绍校园 B 是为了演示可以处理多个校园,并仅授权访问一个校园。校园 A 显示了两个大楼:工程楼和生物楼。每个建筑都有与其校园的关系。在本示例中,两个大楼都具有公共空间、个人办公室和安全实验室。每个空间都具有名称,并与其大楼关联。

下表定义了涉及的组和个体:

表 1. 虚拟校园示例的组和个体
标识类型
校园 A 的用户---
校园 B 的用户---
校园 A 的工程师(工程师)---
校园 A 的生物学家(生物学家)---
校园 A 的清洁工---
Jane Linnaeus个体校园 A 的用户、生物学家
Jim Fermi个体校园 A 的用户、工程师
Stan the Yeti个体校园 A 的用户、生物学家

让我们假设以下访问规则:

  • 只有校园 A 的用户可以进入校园 A;只有校园 B 的用户可以进入校园 B。

并且在校园 A 的用户中:

  • 生物学家和工程师可以进入自己的大楼和其中的公共空间;另外,生物学家可以进入工程楼。
  • 校园 A 的清洁工可以进入任何房间(实验室除外)。
  • 允许 Jim Fermi 进入工程楼中他自己的办公室,即个人办公室 2。
  • 只允许 Jim Fermi 和 Stan the Yeti 进入工程楼中的安全实验室 3。
  • 允许 Jane Linnaeus 进入生物楼中她自已的个人办公室 5,
  • 只有 Stan the Yeti 可以进入生物楼的安全实验室 6。

校园、大楼和房间的保护情况如图 12 所示。

图 12. 虚拟校园示例的 ACL 结构
图 12. 虚拟校园示例的 ACL 结构

注意,每个大楼中的公共区域不需要任何 ACL;当资源的访问规则偏离上一级资源的访问规则时,您只需创建 ACL。例如,您可以添加用于校园 A 的所有用户的剧场,并且不为它创建任何 ACL。

让我们使用一个简短的简化示例来结束本主题,该示例说明如何在实际的应用程序中使用 EAz 接口。假设您已实现虚拟校园系统,并且我们现在正在编写在进入房间时执行的方法。则代码应该与以下所示类似:

清单 x. 示例代码
...
enterRoom(Room room) {
AuthorizationManager.authorize(Permissions.AllActions.ACTION_ENTER_ROOM, 
	WSSubject.getCallerSubject(), aclHolder);
...Do room stuff...
}

结束语

本文介绍了 EAz 的基于 Enum 的授权可以解决的问题、Eaz 的主要公共结构和内部结构,以及应用程序使用 EAz 进行自定义授权决策的典型方法。第 2 部分将详细介绍 EAz 的初始代码,了解 Java 5 Enum 如何实现授权功能。在最后一篇文章中,我们将了解如何使用 EAz 的功能,在基于 JSF 的 Web 应用程序中描述其自身的授权配置,介绍基于 EAz 的各种授权规则的配置。

参考资料

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=283342
ArticleTitle=结合使用基于 Enum 的访问控制列表和 EAz 进行细粒度 Java EE 授权,第 1 部分: 问题空间和 EAz 体系结构
publish-date=01282008