J2EE 同时提供了声明和编程形式的基于角色的授权功能。尽管本文并不打算作为 J2EE 安全方面的教程,但我们将对其主要方面进行简单的总结。
J2EE 声明形式的授权
可将 J2EE 安全角色和约束添加到应用程序的部署描述符。Web 约束添加到 web.xml 中,它们是基于 URL 模式的。EJB 约束添加到 ejb-jar.xml 中,用于定义方法级别的权限。WebSphere Application Server 提供了相应的工具,允许部署人员定义哪些用户和组(按照用户注册中心中的定义)有权访问哪些安全角色。应用服务器运行时使用这些绑定来确定是否向特定的用户授予了某个角色,然后决定是否可访问受保护的资源。
J2EE 编程形式的授权
声明形式的授权简单而强大,但经常并不能提供足够的控制。一旦对用户界面自定义的问题进行分析后,此问题则会变得更为明显。为了提供额外的灵活性,J2EE 提供了 API,以允许开发人员使用下列调用来测试当前用户是否具有访问特定角色的权限:
对于 Servlet 使用 isUserInRole()
对于 EJB 使用 isCallerInRole()
此外,还有用于提供对用户标识的访问的 API 调用:
对于 Servlet 使用 getUserPrincipal()
对于 EJB 使用 getCallerPrincipal()
通过使用这些 API,您可以开发任意复杂的授权模型。在极端的情况下,如果 J2EE 角色信息没有用处,则可以获取用户标识信息,并使用其查找和执行存储在某个其他来源(如数据库或规则引擎)中任意复杂的授权规则。
任何针对以后的 API 编写的代码都可能存在移植性问题,因为 Principal 对象的格式并未标准化,在不同的应用服务器平台上可能会有所不同。
JACC
正如前面所提到的,JACC 提供了一种标准方法来将外部授权提供程序插入到 J2EE 容器中。这样,第三方授权提供程序就能够在用户访问 J2EE 资源时进行授权决策了。
在 JACC 之前,并没有针对应用服务器作出的访问决策的规范。在没有 JACC 的情况下,要使用供应商实现和专用接口进行第三方供应商产品集成。当时并没有标准方法可以使第三方授权提供程序(如 Tivoli Access Manager)插入应用服务器以作出访问决策。为了解决这些问题,Sun Microsystems™ 在 J2EE 1.4 规范中引入了 JACC,从而提供了一项标准机制,以供第三方提供程序从应用程序和应用服务器收集安全策略信息并在运行时实现策略决策。
这些运行时策略决策是通过 java.security.Policy 类作出的。在方法预分派阶段,Web 和 EJB 容器会通过调用 Java Security Manager checkPermission 方法来在运行时执行安全策略;Java Security Manager checkPermission 方法会委托给抽象类中的 implies 方法。JACC 提供程序必须提供实现 implies 方法的具体 Policy 子类。此外,JACC 提供程序还必须实现 PolicyConfiguration 接口,并提供具体 PolicyConfigurationFactory 类。
在部署应用程序时,策略配置权限对象会将自身添加到提供程序中。它们包含容器中配置的与 Web 和 EJB 安全相关的 J2EE 部署描述符信息。这是容器用于将安全策略信息传递给提供程序的机制。
在运行时,如果用户访问 Web 或 EJB 资源,容器将创建相应的 Web 或 EJB 权限对象,并调用提供程序的 Policy 对象的 implies 方法。permission 对象包含有关所访问的资源的信息:如果资源是 Web 资源,则 permission 对象为 URL;如果资源为 EJB,则 permission 对象包含 EJB 对象和方法的名称。封装 Principal 的 Protection Domain 对象也有可能会传递给 Policy 对象。而且,提供程序能够通过容器注册的 PolicyContext 处理程序的 getContext 方法来访问细粒度资源属性,如 EJB 调用的特定参数。提供程序将随后通过向 Policy.implies 方法返回 True 或 False 来负责准许或拒绝对资源的访问。
JACC 最初的目的是为了提供一个标准接口,以便第三方能够插入授权提供程序来作出访问决策。下面让我们看看 JACC 可能能够满足的一系列安全需求。
外部化标准 J2EE 安全模型
标准 J2EE 安全模型包含角色的描述,包括可由主体访问的受安全机制约束的资源。正如前面所提到的,JACC 提供程序可访问此信息,并全面支持这些访问决策的外部化。
扩展标准 J2EE 安全模型
标准模型是静态的。约束和角色在部署受安全保护的应用程序时确定。标准模型中并没有规定如何在部署后修改角色-约束关系。不过,JACC 提供程序并不仅限于标准 J2EE 安全模型的静态特性。JACC 提供程序还具有进行动态的基于运行时的访问决策所必需的自主性和信息。
例如,我们经常要考虑这样的问题:特定角色是否可以对特定的对象执行特定的操作?在讨论 JACC 带来的利弊前,让我们考虑如何将这种关系映射到标准 J2EE 安全性以及 JACC 使用。
标准 J2EE 可以部分地满足此映射,例如,可以将对象 映射到会话 EJB 类,将 操作 映射到特定的安全 EJB 方法。同样,正如我们前面所提到的,此映射是静态 的。
而另一方面,JACC 能够支持上述映射,而且还支持动态的基于实例 的访问控制。例如,可以通过以下方式执行授权检查:将操作参数传递给 EJB 方法调用,以允许动态确定授权检查。我们将在稍后讨论一个示例。
现在,让我们了解一下在考虑 JACC 时需要注意的一些与性能及实现复杂性相关的事项:
在使用 JACC 基于方法参数的访问时可能会存在性能影响。例如,WebSphere Application Server 支持 JACC 规范所要求的所有策略上下文处理程序。不过,由于存在性能影响,除非提供程序专门要求,否则 EJB 参数策略上下文处理程序并不会激活。如果必须为每个 EJB 方法的每个参数创建对象,则会带来性能影响。
实现 JACC 来支持标准 J2EE 安全性之外的功能可能是一项非常复杂的任务。JACC 为实现标准 J2EE 安全模型提供了良好的支持。例如,在 permssion 对象上提供了一些易于使用的有用方法,用于检查传入 permission 对象是否满足 J2EE 部署描述符的策略要求。 另一方面,JACC 并未提供对执行非标准权限检查的支持,而扩展 J2EE 安全模型又将需要这种支持。实现扩展 J2EE 安全模型的 JACC 提供程序是一项复杂的任务,不应轻视。
JACC 配置应用于整个计算单元内。任何 JACC 提供程序都必须满足计算单元内的所有应用程序的授权要求。对于其中一些具有标准 J2EE 特征,而一些又有特殊要求的情况,则必须提供同时满足这两组要求的提供程序——而这就增加了复杂性。
现在让我们简单了解一下如何使用 JACC 处理我们的打印机授权问题。我们可以使用具有以下类似方法的“Printer”会话 Facade EJB:
print(printerName:string, itemToPrint:string)
manageQueue(printerName:string, queueOperation: string)
在我们的 JACC 提供程序内部,可以提供一个 EJB、角色、操作和对象的映射表;例如,通过使用“Printer”EJB,“Managers”能够对“PrinterX”执行“ManageQueue”操作。
能够采用可选的方式使用 J2EE 授权约束对 Facade EJB 方法进行保护。不过,无论是否提供安全保护,都将调用 JACC 提供程序的 Policy.implies 方法。在提供程序内部,一旦完成了常规 J2EE 授权检查,我们就会检查 EJB 是否为映射中使用的 EJB 之一,然后根据我们的映射表来检查 EJB 参数。如果调用已授权,则将从 Policy.implies 方法返回 True。
顺便提一下,在我们的 JACC 提供程序内部,由于我们是在用户的调用线程上进行执行,因此可以通过针对表中的角色(例如,“Managers”)调用常规 J2EE isCallerInRole 方法来检查调用主体是否属于相应角色。
企业安全产品
正如我们前面所讨论的,Tivoli Access Manager 之类的企业安全产品通常设计用于解决一系列授权问题。要实现这个目的,可以采用两种方法,第一种方法是将授权规则从应用程序外部化,并提供 API 来调用授权规则引擎,第二种方法是通过使用 JACC 接口插入 J2EE 容器来透明地调用授权规则引擎,另外,还可以同时使用这两种方法。当产品同时提供 JACC 和基于专用 API 的解决方案时,务必了解每个模型中可用的功能。例如,对于 Tivoli Access Manager,JACC 并没有提供太多的附加值,因为它实际上仅提供了 J2EE 安全检查的委托功能,但却无法将关于访问控制检查的对象的其他信息(如参数)传递给方法调用。在这种情况下,API 方法的功能要丰富得多,但要在可移植性和功能之间进行折衷。
现在让我们回头看看如何使用 Tivoli Access Manager 解决我们的打印机授权问题。
Tivoli Access Manager 提供了定义受保护对象空间和向对象层中任何位置的对象添加 ACL 的功能。在我们的示例中,我们可以为希望保护的打印机定义一个树形结构。ACL 具有与其关联的特定缺省操作(或权限),但如有必要,可以创建自定义操作组。对于我们的场景,可能会随后决定创建一个名为“print_actions”的操作组,并在其上定义专门用于控制打印的权限位,如“q”表示加入打印机的打印队列,“p”表示打印,“v”表示查看打印队列,“d”表示从队列中删除,等等。我们可以随后在对象空间中恰当的位置添加 ACL,并将用户或组添加到这些 ACL 中,以定义可用权限。添加到 ACL 的每个用户或组可以具有不同的活动权限,从而可对任何用户可用的访问权限类型进行适当的细粒度控制。如有必要,我们还可以向任何对象添加受保护对象策略(Protected Object Policy,POP),从而提供额外的保护,如时段访问规则等。
完成了此配置后,剩下的工作就是添加一个简单的 API 来调用应用程序了。这将使用 aznAPI,以便询问 Tivoli Access Manager 是否允许进行某项操作。在此调用中,我们将传递用户凭据、受保护资源的标识,以及所请求的操作,然后 Tivoli Access Manager 将对该对象上的 ACL 和 POP 进行计算,并返回一个 Boolean 值。正如您所看到的,与尝试对基于角色的安全模型进行修改来适应其设计时并未考虑的情况相比,这显然是更为灵活的解决方案。
展望:SAML 和 XACML
尝试确定任何技术将来的发展方向是一件风险很大的事,但安全性断言标记语言(Security Assertion Markup Language,SAML)和可扩展访问控制标记语言(eXtensible Access Control Markup Language,XACML)极有可能成为未来安全策略实现的重要方面。尽管本文并不打算详细描述这些技术,但这里将进行简要的总结,并概括说明在 J2EE 授权领域中这些技术之间的关系。
SAML 通常被认为是一项 Web 服务标准,不过事实上,它却提供了独立于平台和协议的联合标识的总体管理结构。SAML 引入了策略决策点(Policy Decision Point,PDP)和策略执行点(Policy Enforcement Point,PEP)的概念。顾名思义,PEP 是安全体系结构中基于 PDP 中定义的策略执行身份验证和授权规则的位置。XACML 构建于 SAML 之上,提供了用于定义访问控制和授权请求及响应消息的实际语义。SAML 和 XACML 都没有提供用于交付和管理安全服务的基础设施;需由企业安全供应商提供此基础设施。
因此,所面临的问题是:这将如何影响 J2EE 应用服务器的安全性,特别是它如何影响在此领域内的授权?正如我们前面所讨论的,J2EE 应用程序中的授权功能以两种形式提供:应用服务器本身的嵌入式安全引擎和使用 JACC 接口将授权委托给外部提供程序的功能。虽然 J2EE 应用服务器供应商可以在内部使用 XACML 来定义通过应用 J2EE 安全约束创建的授权策略,但这通常是一种貌似神秘的做法,其意义并不大。而更重要的是企业安全解决方案供应商可能支持 XACML——特别是支持请求-响应协议。为了使用目前的 JACC 接口,您需要安装安全服务供应商的特定提供程序,因为并未定义从提供程序到服务的协议。通过使用定义了该协议的 XACML,可以(至少理论上如此)获得能够与任何识别 XACML 的服务进行通信的通用提供程序。
授权层次
在结束本文之前,我们还需要讨论最后一个问题。在分层应用程序(J2EE 应用程序通常属于此类)中,需要在所有层次执行授权——尤其是在最底层,这一点非常重要。例如,如果用户从浏览器发出了访问 Serlvet 的 URL 请求,而 Servlet 又调用 EJB,则在许多情况下,授权都仅发生在 Servlet 层——或者更糟糕,仅在浏览器内的 JavaScript 代码中进行授权。这可能导致系统不安全,因为黑客可以编写代码来直接调用 EJB,而得以绕过 Web 层。类似地,如果 EJB 受到保护,但数据库没有受到保护,则黑客完全可以绕过应用程序,直接进入核心地带操纵公司的数据。务必考虑系统的每个层次的可能访问路径,同时还要考虑授权如何与其他安全功能(如网络级别的保护)集成。
结束语
本文说明了 J2EE 应用程序授权中涉及的一些问题,并介绍了 WebSphere Application Server 和其他 J2EE 应用服务器的一些现有标准功能。尽可能使用现有标准将最为有利。虽然现有标准并未提供完整的解决方案,但更为明智的方法是尽可能地对其加以利用,并随后扩展其功能,而不要使用自定义解决方案完全将其替换。授权是一个复杂而颇有难度的主题,因此要慎重地考虑各方面的问题。