什么是授权?
授权就是基于用户的权限或用户的类别(在这种情况下,“用户”可能指某个外部系统,而不一定是个人)管理对受保护系统资源的访问。因此,授权假定已进行了身份验证,因为如果不知道用户是谁,则根本无法提供恰当的访问控制——而不是在通常情况下所有用户都具有相同的权限。稍后我们将详细说明各种授权模式或方式,但首先我们将对其做一个简要的总结。
基于角色的授权
基于角色的授权基于用户是某类用户的成员这一事实提供对资源的访问。这通常依赖于用户具有在用户注册中心定义的组标识。此方法定义受保护系统的各个组件,并将这些组件映射到能够对其进行访问的用户组。这种类型的授权是 J2EE 领域的标准模式。总的说来,这是一种非常粗粒度的安全形式,用于处理对系统的功能领域的访问,可以由 URL 或对 EJB 组件的方法调用进行定义。这并不是说无法使用角色定义更细粒度的保护,但这样做的效果通常不好。
基于实例的授权
与 J2EE 基于角色的授权通常提供的授权相比,此模式提供了更细粒度的授权,可以一直细化到项目内的单个对象的级别。我们现在讨论的是数据保护,而不只是功能保护。
基于关系/所有权的授权
这种类型的授权是基于实例的模式的一个特例,在基于实例的模式中,在应用程序的数据结构内存在用户与某些其他受保护的数据之间的所有权关系。这通常意味着必须将授权规则嵌入到应用程序本身的数据访问逻辑之中。
用户界面自定义
虽然这不是真正的授权类型,但与此紧密相关的一个问题是对用户界面进行自定义,以使其仅显示允许特定用户看到的特定功能。例如,菜单或门户页将不会显示未授权用户访问的链接或 Portlet。这实际上是一种预先授权;授权询问“我能这样做吗?”,而预先授权则询问一个略为不同的问题“如果我询问是否能进行此操作,答案会是什么?”不过,正如我们稍后将看到的,这两种情况下的编码技术可以完全相同。
J2EE 基于角色的授权
基于角色的授权通过仅将资源提供给已分配相应角色的用户来保护资源。J2EE 提供了通过使用声明和编程技术来完成此任务的标准方法。我们稍后将在授权技术部分中详细讨论 J2EE 安全性。不过,务必理解 J2EE 领域中的角色概念。关键要记住,J2EE 角色不是一组人员。它们是特定于应用程序的逻辑名称,可以在部署时映射到用户和组。角色本身通常表示 J2EE 应用程序中的权限集合。
实际定义哪些用户属于哪些组是在用户注册中心中进行管理的。这可以是企业 LDAP 服务器,也可以是仅由单个应用程序使用的数据库,但用户和组信息的存储库位于 J2EE 环境之外。在 J2EE 应用程序内,您可以基于这些角色定义和命名安全角色及约束资源访问。因此,角色是权限集合;为了使用它,必须在部署应用程序时将其绑定到某一组用户。可以按名称将其映射到一个或多个用户,也可以映射到一个或多个用户组,还可以映射到二者的任意组合。术语主体通常是指用户或用户组。
您将看到,虽然 J2EE 角色可能通过简单的一对一映射紧密地映射到注册中心组,但并不一定非得如此;在最简单的情况下,可以将“manager”角色映射到称为“manager”的组,但 J2EE 角色的强大之处在于,它提供了出色的灵活性,能够在组织发生更改时更改绑定,而无需进行编程更改。以下列情况为例,假定特定的应用程序功能仅对法律部门的员工可用,而这些员工都属于 Department 102。可以在应用程序中创建一个名为“legal”的 J2EE 角色,并绑定到注册中心组“Dept102”。如果以后对该部门进行了重组,其中一半的员工转到了 Department 507,则可以更改绑定,以映射到“Dept102”和“Dept507”。然后,当属于这两个部门的任何员工经过了系统的身份验证后,就会向其授予“legal”角色,系统会向其提供恰当的访问权限。
自定义基于角色的授权
即使使用编程 API,仍然会有因 J2EE 角色模型不够灵活而难以满足业务需求的情况。不过,在草率行事前,应该对以 J2EE 模型为基础(而不是将其替换)进行构建的可能性进行分析。这样做的优势包括:
利用本机应用服务器身份验证对创建完全安全的系统至关重要。(请参见 IBM WebSphere 开发者技术期刊: WebSphere Application Server V6 高级安全性加强——第 1 部分)。
良好的安全性可提供深度防御;使用多种方法并不一定是坏事。
可以首先使用 J2EE 执行基本的声明性安全功能,然后使用自定义方法来执行更为详细的身份验证逻辑。
从头编写可以免费获得的功能并不划算!
另外要注意,有时候,可以通过应用服务器安全运行时的其他可插入功能来获得所需的灵活性。例如,如果需要角色-组映射在运行时具有动态性(根据用户登录时提供的信息),则可以使用 JAAS 登录扩展来满足此需求(请参见 Advanced authentication in WebSphere Application Server)。在 WebSphere Application Server V5.1.1 或更高版本中,可以在 Trust Association Interceptor 或 JAAS 登录模块中创建动态组成员身份。不过,如果角色与上下文相关(基于具体的应用程序使用情况),则可能需要购买或构建授权框架。医疗应用程序就是上下文使用模式的一个例子:医生可以一次性登录到系统,但根据所查看的病人或医疗实体(如医院或诊所)的上下文的不同,该医生的角色可能会从检查医师更改为主治医师。此更改是在会话过程中动态更改的,而不仅限于进行身份验证时可用的信息。
只要认为 J2EE 应用程序授权本身不足以解决您的问题,下一个问题就是要确定是否构建或购买授权解决方案。IBM Tivoli® Access Manager 等企业安全产品提供了灵活的、基于策略的功能授权。不过,将这些功能与应用服务器相集成可能有一定的挑战性。您需要考虑的一些问题包括:
是否使用专用 API?
将提供何种功能?
授权请求外部化会带来何种性能损失?
Java Authorization Contract for Containers (JACC) 是一种基于标准的方法,用于将外部安全管理器与应用服务器集成。JACC 提供了将安全授权的权限检查委托给外部提供程序的功能。由于授权检查是在容器将控制权交给应用程序前进行的,因此 JACC 具有能够清楚区分自定义授权逻辑和应用程序逻辑的优势,从而满足了关注点分离的需求。不过,使用 JACC 时有一些方面需要特别注意,因此有必要更深入地探讨 JACC,以准确了解其执行的工作以及可对其加以应用的场合。我们稍后将对 JACC 进行更为详细的讨论。
基于实例的授权
顾名思义,基于实例的授权就是将访问权限授予某个对象的特定实例。基于实例的授权通常使用访问控制列表(Access Control List,ACL)来保护实例,而 ACL 存储在某种类型的策略存储区中,并且可用来制定访问决策。可以将 J2EE 角色作为 ACL 使用,不过这个方法可能会不方便。正如我们前面所讨论的,J2EE 角色终究只是一个名称,是可以绑定到任何一组主体的逻辑构造。这种方法不能很好地扩展来大量使用 ACL,也不能处理在应用程序执行时动态修改 ACL 的情况;因为我们曾提到,部署描述符在应用程序启动时以静态方式定义 J2EE 角色。考虑到这些限制,在许多情况下,更好的方法可能是使用外部安全解决方案或 ACL 基于实例的安全性自定义框架。
为了更便于理解,让我们看一个例子。假定我们有一个新的 J2EE 人力资源系统,可允许用户执行打印相关的任务,如将文档发送到打印机和查看与管理打印队列。由于可打印数据的敏感特征,需要进行某些限制。例如,可能存在有关授权哪些用户使用或管理哪些打印机的规则。还可能存在有关在每天的特定时段使用打印机的规则;或许会考虑连夜在特定的打印机上打印敏感材料。
我们可以使用标准的 J2EE 基于角色的安全性来管理打印机访问。例如,可以为打印机和所需访问类型的每种组合定义一个角色,最终得到“Allowed_to_print_to_PrinterX”或“Allowed_to_manage_queue_for_PrinterY”之类的角色。然后,我们必须在应用程序中编写代码,以使用 isUserInRole() 调用遍历所有可能的角色,从而验证当前用户是否被授权执行他们所尝试的任何打印相关操作,而且我们必须将每个角色绑定到相应的用户注册中心条目。
正如您所看到的,这并不是授权需求的一个条理非常清楚的实现。添加任何新打印机都会要求对应用程序进行更改;而有关何人可以进行何种操作的规则的变化则会要求重新将这些角色绑定到主体,并重新部署应用程序。此外,我们尚未开始考虑时段问题。我们需要能够存储与每台打印机对应的可配置信息,但在标准 J2EE 授权模式中,实际上却没有任何地方能够放置此类信息。
现在,如果我们要使用独立的授权服务,这可以极大地简化解决方案,因为我们将只需要询问授权服务,当前用户是否可以对某个对象执行某项操作。所有这些都将外部化,从而极大地简化了编程模型,并使得对外部提供程序的更改以实时的方式反映到正在运行的应用程序中。我们稍后将再次对此问题进行讨论。
所有权关系
直接所有权是用户和某些受保护数据间的一种十分常见的基于实例的关系。例如,在经纪业应用程序中,理财顾问可以查看其个人客户的帐户,但却不能查看公司的其他客户的帐户。部门经理可以查看所属的理财顾问的所有客户的所有帐户,但却不能查看其他部门的客户的帐户,诸如此类。在这种情况下,权限内置到应用程序的数据结构中。
应用程序很少会采用以下这种方式,即向用户界面提供用于检索所有数据的帐户列表,然后再向每一行应用基于 ACL 的权限。这里存在的主要问题是性能:与在 Java 代码中进行筛选相比,在数据库引擎中进行行筛选通常要快得多。尽管缓存可能在一定程度上减轻这种性能影响,但如果使用特定大小的数据块检索数据来支持用户界面分页,则可能出现另一个问题。如果筛选在应用程序中进行,则无法知道请求了多少行,因此可能导致通过多次调用来获取单页数据的情况。
遗憾的是,尽管存在种种缺陷,这种应用程序端筛选方法却被经常使用。如果应用程序使用对象关系映射工具来执行数据访问,然后尝试以某种通用方式将安全机制应用到实例化的类,则尤其可能出现问题。这是对计算资源的一种极为低效的使用,获得可接受的性能和可伸缩性的可能性非常小。
唯一合理的方法是让数据库进行筛选。如果数据库知道最终用户的标识,或通过修改对实际数据访问逻辑的筛选将授权逻辑嵌入到应用程序中(通常通过将相应的项目添加到 SQL 语句的 Where 子句中),则可以通过利用本机数据库授权来实现此目的。取决于应用程序复杂性,或许可以开发一个使用元数据描述授权规则的自定义框架,以自动将安全更改应用到 SQL。如果将存储过程用于数据访问,则这些存储过程也是应用这些规则的最佳场所。
用户界面自定义
动态修改用户界面,以仅向用户显示其能够执行的操作,这样的做法常常也被视为授权的一个方面。虽然这很有争议,但它无疑是一个常见的问题;向用户显示他们未被授权执行的链接的做法肯定非常不好(并可能招致黑客攻击)。
自定义 UI 以删除链接和菜单选项的做法与基于角色的授权非常相似。这通常只是简单地使用 isUserInRole() 调用来遍历各项,从而验证用户是否应该看到此项。还可以采用相同的方式自定义表单上的实际数据,不过这可能会导致进行冗长烦琐的编程,而且不能提供足够的灵活性,具体取决于业务需求。当需要将访问控制自定义与其他类型(如用户首选项)集成时,这种方法可能会变得尤为复杂。如果用户能够访问允许开发自己的特定 UI 组件视图的某种首选项工具,则该工具也需要识别授权规则元数据,以防止用户向其个性化表单添加其不应该具有访问权限的项。正如前面讨论自定义基于角色的授权时所提到的,可能会存在数据视图与上下文相关的情况,并不能仅简单地基于静态角色对其进行调整。
【编辑推荐】