狼爱上狸

我胡汉三又回来了

acegi,IBM的Acegi Security System(3)

2007 年 10 月 18 日

本文是 Acegi Security Systerm 介绍的最后一部分(共三部分),Bilal Siddiqui 将向您介绍如何保护对 Java 类实例的访问,从而结束本系列文章。通过本文了解为何需要对 Java™ 类的访问进行保护,Spring 如何创建和保护对 Java 类实例的访问以及如何对 Acegi 进行配置以实现 Java 应用程序的类安全性。

这期共分三部分的系列文章介绍了如何使用 Acegi 安全系统保护 Java 企业应用程序。系列文章的 第 1 部分 简单介绍了 Acegi 并解释如何使用其内置的安全过滤器实现一个简单的、基于 URL 的安全系统。第 2 部分 介绍了如何编写访问控制策略并将其保存到一个 LDAP 目录服务器,以及如何配置 Acegi 来与目录服务器进行交互,从而实现访问控制策略。第 3 部分(也是本系列的最后一篇文章)将演示如何在企业应用程序中使用 Acegi 保护对 Java 类实例的访问。

首先我将介绍何时需要对 Java 类访问进行保护,包括文中引用的两个典型企业应用程序场景。之后,我将解释 Spring 的反转控制(IOC)框架如何创建可从 JSP 或 servlet 访问的 Java 类实例。我还将介绍有关 bean 代理 的重要概念,Spring 正是使用它过滤对 Java 类的访问。最后,我将介绍如何对 Acegi 的方法安全性拦截器进行配置以控制对 Java 类的访问。我将对 第 2 部分 中的示例程序进行增强,为实现安全的 Java 对象提供支持,从而结束本系列的最后一篇文章。

由于本文的讨论构建在本系列前两部分的内容之上,因此会经常引用到 第 1 部分第 2 部分 中的讨论和示例。因此,在继续阅读本文之前,在其他浏览器窗口中打开前两期文章将有助于理解本文内容。

保护 Java 类的用例

您可能还记得,我曾在本系列的开头部分简单介绍了 企业应用程序安全性。在那次讨论中我曾提到过一种场景,其中 URL 安全性并不能完全满足这种场景的安全需求:

假设有这样一个 PDF 文档,其中包含了某制造业公司生产的特定产品的数据。文档的一部分包含了设计数据,将由公司设计部分进行编辑和更新。文档另一部分包含生产经理将使用到的生产数据。对于此类场景,需要实现更加细粒度的安全性,对文档的不同部分应用不同的访问权限。

在继续阅读之前,请考虑更多的应用程序场景,除了实现 URL 安全性以外,这些场景还要求您对单独的类访问进行保护。

业务自动化

业务自动化应用程序中的工作流由多个流程组成。例如,病理学实验室中执行血液测试的工作流由若干个步骤组成,其中每个步骤可看作一个业务流程:

  1. 工作人员从病人处采集血液样本并为其分配一个 ID。
  2. 实验室技术人员对样本进行必要的测试并准备测试结果。
  3. 由具备相应资格的病理学专家根据测试结果编写测试报告。

很明显,每个流程分别由单独的授权用户执行。未授权的用户则无权执行流程。例如,实验室研究人员只负责准备试验结果,而无权编写测试报告。

几乎所有的业务自动化应用程序都普遍使用授权的业务流程。通常,每个业务流程被实现为一个 Java 类,并且需要使用合适的访问控制策略对所有类实施保护。

企业对企业(Business-to-business)集成

Business-to-business (B2B) 集成指一种常见的场景,其中的两个企业实体需要彼此公开各自的特定功能。例如,宾馆可能向旅游公司公开其房间预订功能,而后者使用该功能为游客预订空闲的房间。作为合作伙伴的旅游公司可能具有一个特定的订房率。在这个场景中,宾馆的订房系统必须先对旅游公司进行身份验证,然后才能允许他们访问所选择的类,以便按照特定的订房率进行房间预订。





回页首


使用 Spring 创建 Java 对象

现在您已经了解了对 Java 类示例的访问进行保护的重要性。在介绍能够实现更高级安全性的 Acegi 新功能之前,我将引导您回顾 Spring 框架的几个关键特性,您需要了解这些内容才能继续后文的示例。

首先对一些 Java 类进行配置并执行实例化。第 1 部分 曾介绍过,Java 类在 Spring 的 XML 配置文件中进行配置。在 Spring 配置文件中配置 Java 类的过程与 Acegi 过滤器的配置过程完全相同,因此这里不多做介绍。相反,我们将查看清单 1,它展示了名为 publicCatalog 的 bean 的配置:


清单 1. Acegi XML 配置文件
            <beans>
            <bean id="publicCatalog"
            class="com.catalog.PublicCatalog" />
            <!--Other bean tags -->
            <beans>
            

了解 Spring 的 IOC 框架如何从 XML 配置文件读取 Java 类信息以及如何进行实例化,这一点非常重要。您可能还记得,我在系列文章的 第 1 部分 中使用一个 web.xml 文件配置 <listener> 标记,它指向名为 ContextLoaderListener 的类。ContextLoaderListener 装载 Spring 的 IOC 框架并创建 Java 对象。您可以参考 第 1 部分的清单 8 查看全部内容。图 1 也对此进行了描述:


图 1. 装载 Spring 的 IOC 框架并创建 Java 对象
装载 Spring 的 IOC 框架并创建 Java 对象的步骤

现在我们将详细讨论这些步骤:

  1. 当初始化 Acegi 应用程序时,servlet 容器(本例中为 Apache Tomcat)创建了一个 servlet 上下文,其中保存了有关应用程序资源的信息,例如 JSP 页面和类。

  2. servlet 容器通知 ContextLoaderListener 类应用程序正在启动。

  3. ContextLoaderListener 类创建一个 Web 应用程序上下文以保存应用程序中特定于 Spring 的资源信息。借助 Spring 的 IOC 框架,您可以装载自己的自定义应用程序上下文。要创建应用程序上下文,将使用名为 ContextLoader 的上下文装载器类装载应用程序上下文。

  4. 如果应用程序不需要定义自己的应用程序上下文,则可以使用名为 XMLWebApplicationContext 的类,它是 Spring 框架的一部分并提供可处理 Spring XML 配置文件的功能。Acegi 应用程序使用的是 Spring 的 XML 配置文件,因此本文仅讨论由 XMLWebApplicationContext 类表示的应用程序上下文。在本例中,上下文装载器对 XMLWebApplicationContext 类进行实例化,后者表示您的 Acegi 应用程序的应用程序上下文。上下文装载器还在 Web 应用程序上下文中设置 servlet 上下文(于步骤 1 中创建)的引用。

  5. XMLWebApplicationContext 类对 XML 配置文件进行解析,获得关于 Java 类的信息并将信息装载到其他内部对象中。

  6. XMLWebApplicationContext 类对 XML 配置文件中指定的所有 Java 类进行实例化。XMLWebApplicationContext 类检查 XML 配置文件中经过配置的 Java bean 是否依赖其他的 Java 对象。如果是的话,XMLWebApplicationContext 类将首先对其他 bean 所依赖的 bean 进行实例化。通过这种方式,XMLWebApplicationContext 类创建了 XML 配置文件中定义的所有 bean 的实例。(注意,步骤 6 假定 XML 配置文件中所有 bean 都不要进行保护,稍后一节将介绍步骤 5 和步骤 6 之间执行的额外步骤,从而保护对此处创建的 Java bean 的访问)。

  7. XMLWebApplicationContext 类将所有 bean 保存在一个数组中。

您现在已了解到如何从 XML 配置文件中装载 bean 定义并创建 Java 类的实例。接下来,我将向您介绍 Spring bean 代理并解释它对于保护 Java 类实例的重要性。





回页首


使用 bean 代理

上一节讨论了 Spring 的 IOC 框架对 Java 对象进行实例化。要保护对 Java 对象的访问,Spring 的 IOC 框架使用了 bean 代理 的概念。本节首先介绍如何配置 bean 代理,然后演示 Spring 的 IOC 框架如何创建代理对象。

为 Java 对象配置代理

如果希望创建 bean 代理,Spring IOC 框架要求您对代理创建器 bean 的实例进行配置。Spring 的 IOC 框架使用代理创建器创建代理对象。清单 2 为代理创建器 bean 的配置文件,用于保护名为 privateCatalog 的 Java 对象:


清单 2. 代理 bean 配置
            <bean id="proxyCreator"
            class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames">
            <list>
            <value>privateCatalog</value>
            <!--Names of other beans to be proxied -->
            </list>
            </property>
            <property name="interceptorNames">
            <list>
            <value>privateCatalogSecurityInterceptor</value>
            </list>
            </property>
            </bean>
            

如清单 2 所示,<bean> 标记具有一个 class 属性,其值为 org.springframework.aop.framework.autoproxy. BeanNameAutoProxyCreatorBeanNameAutoProxyCreator 类是 Spring IOC 框架的一部分,可以自动创建 bean 代理。Spring 框架提供了 BeanPostProcessor 接口,它提供了一种可扩展机制,允许应用程序编写自己的逻辑来创建 bean 代理。Spring 的 BeanNameAutoProxyCreator 类实现了 BeanPostProcessor 接口并提供所有必需的代理创建逻辑来保护 Java 类。因此,本文中您无需实现 BeanPostProcessor 接口。

在创建 bean 代理时,BeanNameAutoProxyCreator 类为所有由 beanNames 属性定义的 bean 创建代理(参见 清单 2<bean> 标记的第一个 <property> 子元素)。beanNames 属性在 <list> 标记中包含一个 bean 名称列表。在 清单 2 中,我只对希望为之创建代理的 privateCatalog bean进行了配置。

现在查看 清单 2<bean> 标记的第二个 <property> 子元素。它指定了名为 interceptorNames 的代理,它将一个或多个拦截器的名称封装起来。我将在后文详细讨论拦截器概念。现在,只需了解拦截器可以拦截用户并在用户访问 bean 之前实现访问控制策略。

现在,您已了解了如何对希望进行保护的 bean 配置代理。接下来,您将了解 Spring 的 IOC 框架如何在内部为应用程序的 bean 创建代理对象。

Spring IOC 发挥效用

在 “使用 Spring 创建 Java 对象” 的步骤 5 和步骤 6 中,您了解了 XMLWebApplicationContext 类如何从 XML 配置文件中读取 bean 定义并随后创建 bean 实例。在创建 bean 实例之前,XMLWebApplicationContext 类将检查 XML 配置文件是否包含任何代理创建器 bean(即实现 BeanPostProcessor 接口的 bean)配置。如果存在该 bean,它将要求代理创建器为您希望进行保护的 bean 创建 bean 代理。

现在考虑代理创建器如何在内部创建代理对象:

  1. 代理创建器(即 BeanNameAutoProxyCreator 类)装载 清单 2 中配置的 beanNames 属性文件中指定的所有 bean 名称。

  2. 代理创建器使用 bean 名称装载各自的 Java 类,这些类使用了每个 bean 定义的 class 属性。

  3. 代理创建器创建 清单 2 所示的 interceptorNames 属性中指定的拦截器的实例。

  4. 最后,代理创建器创建一个 Cglib2AopProxy 类的实例,将所有 bean 名称(步骤 2)和拦截器(步骤 3)传递到 Cglib2AopProxy 类。Cglib2AopProxy 类是 Spring 框架的一部分并用于生成动态代理对象。在本例中,Cglib2AopProxy 类将创建安全 bean 访问控制所需的代理对象。

Cglib2AopProxy 类实现了两个名为 AOPProxyMethodInterceptor 的接口。AOPProxy 接口由 Spring 框架提供,表示您希望进行代理的实际 bean,因此它与您的 bean 公开相同的方法。MethodInterceptor 接口也源于 AOP 框架,它包含的方法可以在用户试图访问您已执行代理的 bean 时接受控制权。这意味着 MethodInterceptor 接口处理来自用户的请求以访问执行过代理的 bean。由于 Cglib2AopProxy 类同时实现了 AOPProxyMethodInterceptor 接口,因此它提供了完整的功能,既可以提供经过代理的 bean,也可以处理用户请求以访问代理 bean(参见 参考资料小节 中有关 AOP 的讨论文章的链接)。

执行完前面的步骤后,您现在具有了所需的代理对象。因此 XMLWebApplicationContext 类将安全 bean 的代理(而不是实际的 bean)保存在 “使用 Spring 创建 Java 对象” 的步骤 7 中的同一个数组中。





回页首


访问执行过代理的 Java 对象

在前面的几节中,您了解了 Spring 如何创建公有 bean 和私有 bean。出于本文的目的,您可将公有 bean 视为使用代理保护的不安全的私有 bean。现在我们来看一下客户机应用程序为访问公有 bean 和私有 bean 而必须遵循的一系列步骤。

清单 3 展示了 publicCatalogprivateCatalog 两个 bean 的 XML 配置。publicCatalog bean 意味着公共访问,因此不需要使用 bean 代理。privateCatalog bean 意味着只能由指定用户访问,因此必须加以保护。我在清单 3 中包含了 privateCatalog bean 的 bean 代理配置:


清单 3. publicCatalog 和 privateCatalog bean 的 XML 配置
            <beans>
            <bean id="publicCatalog" class="sample.PublicCatalog"/>
            <bean id="privateCatalog" class="sample.PrivateCatalog"/>
            <!-- proxy configuration for privateCatalog bean -->
            <bean id="proxyCreator"
            class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames">
            <list>
            <value>privateCatalog</value>
            <!--Names of other beans to be proxied -->
            </list>
            </property>
            <property name="interceptorNames">
            <list>
            <value>privateCatalogSecurityInterceptor</value>
            </list>
            </property>
            </bean>
            <beans>
            

应用程序可以使用清单 4 中的代码访问清单 3 中配置的 publicCatalogprivateCatalog Java bean。注意,清单 4 中显示的 Java 代码可位于 JSP 页面或位于服务器端 Java 应用程序的 bean 中。


清单 4. 访问安全和不安全 Java bean 的客户机应用程序代码
            //Step 1: Fetching an instance of the application context
            XMLWebApplicationContext applicationCtx =
            WebApplicationContextUtils.getWebApplicationContext(
            this.getServletConfig().getServletContext());
            //Step 2: Fetching an insecure bean from the application context
            PublicCatalog publicCatalog =
            (PublicCatalog) applicationCtx.getBean("publicCatalog");
            //Step 3: Calling a method of the insecure bean
            String publicData = publicCatalog.getData();
            //Step 4: Fetching a secure bean from the application context
            PrivateCatalog privateCatalog =
            (PrivateCatalog) applicationCtx.getBean("privateCatalog");
            //Step 5: Calling a method of the secure bean
            String privateData = privateCatalog.getData();
            

下面将进一步讨论清单 4 中的步骤:

  • 步骤 1:取回一个应用程序上下文实例
    当应用程序希望访问 XML 配置文件中配置的 Java bean 时,它必须取回您在 “使用 Spring 创建 Java 对象” 的步骤 4 中见到的 XMLWebApplicationContext 对象。XMLWebApplicationContext 对象包含对 XML 配置文件配置的所有 Java beans 的引用。

  • 步骤 2:从应用程序上下文中取回不安全的 bean
    您现在具有一个对 XMLWebApplicationContext 对象的引用。XMLWebApplicationContext 类公开了一个 getBean() 方法,它包含 bean 的名称并在数组中查找 “使用 Spring 创建 Java 对象” 步骤 7 中准备的 bean。在本例中,该 bean 为 publicCatalog(未执行过代理),因此 XMLWebApplicationContext 将返回实际的 bean。

  • 步骤 3:调用不安全 bean 的方法
    现在您可以调用步骤 2 中获得的 publicCatalog bean 的任何方法。例如,清单 4 显示的 getData() 方法调用的执行没有应用任何访问控制并向应用程序返回类别数据。

  • 步骤 4:从应用程序上下文取回安全 bean
    安全 bean 与不安全 bean 的取回方式类似,惟一区别是:当您通过调用 getBean() 方法尝试取回安全 bean 时,您将获得安全对象的代理而不是实际的对象。该代理就是我在 “Spring IOC 发挥效用” 步骤 4 中解释的由 Spring 框架创建的同一个对象。

  • 步骤 5:调用安全 bean 的方法
    当调用安全 bean 的方法时,您在 步骤 4 中获得的代理对象将一个方法调用请求分配给拦截器。拦截器将检查试图访问方法的用户是否具有相应的访问权,从而处理方法调用请求。

您现在应该对 Spring 框架如何创建 Java 对象以及客户机应用程序如何与之交互有了清晰的了解。了解了这些内容后,就更加容易理解并利用 Acegi 的方法安全性拦截器,下一节将具体介绍该主题。





回页首


配置 Acegi 的方法安全性拦截器

只要应用程序试图访问由 Acegi 安全系统保护的 bean 方法,请求将被自动传递到 Acegi 的方法安全性拦截器。方法安全性拦截器的作用就是控制对安全 Java bean 的方法的访问。拦截器使用 Acegi 的身份验证和授权框架确认用户是否具有权利调用安全 Java bean 的方法,然后相应地作出响应。

清单 5 展示 Acegi 的方法安全性拦截器的示例配置:


清单 5. Acegi 的方法安全性拦截器的示例配置
            <bean id="privateCatalogSecurityInterceptor"
            class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
            <property name="authenticationManager">
            <ref bean="authenticationManager"/>
            </property>
            <property name="accessDecisionManager">
            <ref bean="accessDecisionManager"/>
            </property>
            <property name="objectDefinitionSource">
            <value>
            sample.PrivateCatalog.getData=ROLE_HEAD_OF_ENGINEERING
            <!-- Roles required by other beans -->
            </value>
            </property>
            </bean>
            

清单 5 所示的拦截器配置包含三个需要进行配置的属性,可以保护对 Java bean 的访问:authenticationManageraccessDecisionManagerobjectDefinitionSource

回忆一下,