这个 系列 由五部分组成,介绍了 Acegi Security System,并演示了如何使用 Acegi 保护企业级 Java 应用程序。本文是该系列的最后一部分,将继续讨论使用 Acegi 保护 JSF 应用程序。在 第 4 部分 中,我介绍了如何在不编写 Java 代码的情况下使用 Acegi 保护 JSF 页面。我还详细说明了部署 JSF-Acegi 应用程序和用户访问该程序时发生的事件。在本部分中,我将着重介绍在 JSF 应用程序中保护 JavaBean 的技术。
首先展示如何将 第 3 部分 中演示的 bean 安全性概念应用于 JSF 应用程序,其效果不是太理想。然后演示两项新技术,这些新技术特别适合在 JSF 应用程序中保护 JavaBean。最后,总结四点策略,可以让您不用编写任何 Java 代码就能够使用 Acegi 在 JSF 应用程序中保护 bean。
在 JSF 应用程序中使用安全 bean 的最简单方法就是,执行 第 3 部分 的清单 4 中介绍的五个步骤。在第 3 部分中,我从 servlet 上下文中取出了 Spring 框架的 Web 应用程序上下文对象。可以在以后使用 Web 应用程序上下文安全地访问 bean。下面的 清单 1 演示了如何在 JSF 页面中使用 Web 应用程序上下文:
清单 1. 从 servlet 上下文提取 Web 应用程序上下文,并将其用于 JSF 页面
可以看到,清单 1 使用名为 WebApplicationContextUtils
的类提取 Web 应用程序上下文的实例。WebApplicationContextUtils
是 Spring 提供的一个工具类。
在得到 Web 应用程序上下文之后,能够调用它的 getBean()
方法得到在 Acegi 配置文件中配置的任何 bean。然后可以调用该 bean 的 getter 方法,并将 getter 方法返回的数据以参数的形式存储在 servlet 请求对象中。这些步骤允许 清单 1 中的 <outputText>
标签向用户提供数据。
像 清单 1 那样直接管理 bean 数据虽然简单,但并不可取。这个方法违反了 JSF 的模型-视图-控制器(MVC)架构(请参阅 参考资料),MVC 架构要求使用模型 bean 保存应用程序数据。所以最好不要在 JSF 应用程序中使用这种策略,除非在非常简单的情况下。
|
JSF 提供了管理应用程序模型 bean 的丰富功能。这类 bean — 称为托管 bean — 被应用于大多数 JSF 应用程序,所以大多数实际的 JSF 应用程序都需要保护托管 bean。
本文余下部分将讨论在 JSF 应用程序中使用安全 bean 的两个策略:
- 使用 Acegi 保护 JSF 托管 bean
- 使用直接由 Acegi 在 JSF 标签中保护的 反转控制(IOC)bean
请看 清单 2 所示的 JSF 页面,其中使用了一个名为 catalog
的 JSF 托管 bean:
清单 2 使用了两个 JSF <outputText>
标签。第一个 <outputText>
标签有一个 #{catalog.publicData}
的 value
属性,第二个标签有一个 #{catalog.privateData}
的 value
属性。这两个标签使用 catalog
bean 的 publicData
和 privateData
属性,它们分别提供公共和私有的编目数据。
在 第 3 部分 的 “访问执行过代理的 Java 对象” 小节中,我配置了两个 Acegi bean,分别名为 publicCatalog
和 privateCatalog
。现在我要将第 3 部分的 publicCatalog
bean(不受保护的供公共访问的 bean)映射到 catalog
bean 的 publicData
属性。类似的,将第 3 部分的 privateCatalog
(在 第 3 部分 的清单 3 中配置的受保护且执行过代理的 bean)映射到上面 清单 2 的托管 bean catalog
的 privateData
属性。映射完成后,catalog
bean 就会充当 JSF 编目应用程序的公共和私有数据的包装器。
清单 3 演示了如何定义 catalog
bean,以便将它的 publicData
和 privateData
属性分别映射到 Acegi 的 publicCatalog
和 privateCatalog
bean:
清单 3. 将 catalog
的属性映射到 Acegi 的 bean
清单 3 实际上演示了 JSF 的一个配置文件。它的根标签是 <faces-config>
,这是大多数 JSF 程序员都熟悉的标签。根 <faces-config>
标签包含两个子标签,名为 <managed-bean>
和 <application>
。现在我要详细解释这两个标签。
清单 3 的 <managed-bean>
标签定义了 catalog
bean 和它的属性。<managed-bean>
标签有三个子标签 — <managed-bean-name>
、<managed-bean-class>
和 <managed-bean-scope>
— 以及两个 <managed-property>
标签。前两个子标签分别定义了 bean 的名称(catalog
)和类(sample.Catalog
)。
清单 3 中的每个 <managed-property>
标签定义 catalog
bean 的一个属性。每个 <managed-property>
标签有两个子标签 — <property-name>
和 <value>
— 分别定义了属性的名称和值。从 清单 3 可以看出,第一个属性的名称是 publicData
,它的值是 #{publicCatalog.data}
。类似的,第二个属性的名称是 privateData
,它的值是 #{privateCatalog.data}
。
这两个值实际上是表达式,分别解析为其他托管 bean 的属性。第一个表达式(#{publicCatalog.data}
)商业智能 publicCatalog
bean 的 data 属性。类似的,第二个表达式(#{privateCatalog.data}
)解析为 privateCatalog
bean 的 data 属性。
JSF 提供了一种机制,能够将 #{publicData.data}
这样的表达式解析为实际的托管 bean 实例。我将会讨论 JSF 的表达式-解析(expression-resolving)机制(在 “定义表达式商业智能器” 小节)。
但是,这里有一个问题。清单 3 的 JSF 配置文件不包含名为 publicCatalog
和 privateCatalog
的托管 bean。我在 第 3 部分 的 “访问执行过代理的 Java 对象” 小节中配置了 publicCatalog
和 privateCatalog
IOC bean(不是 JSF 托管 bean)。所以,JSF 表达式-解析机制必须能够解析为 Acegi 的 IOC bean。
JSF 的 javax.faces.el.VariableResolver
类是默认的表达式解析器,能够将表达式解析为 JSF 的托管 bean。但是,VariableResolver
不能解析为 IOC bean。
JSF 提供了一种扩展机制,允许应用程序开发人员编写自己的表达式解析器。Spring 在名为 org.springframework.web.jsf.DelegatingVariableResolver
的类中提供了 JSF 表达式解析器。DelegatingVariableResolver
类能够将表达式解析为 IOC bean。DelegatingVariableResolver
也用默认的 VariableResolver
将表达式解析为 JSF 托管 bean。
要使用 Spring 的 DelegatingVariableResolver
,必须在 JSF 的配置文件中配置它。这正是在 清单 3 中包含 <application>
标签的目的(清单 4 显示了这个标签,用于快速参考):
清单 4 中的 <application>
标签只包含一个子标签,名为 <variable-resolver>
,用于为 JSF 应用程序配置外部解析器。<variable-resolver>
标签包装了 Spring 解析器类的名称(org.springframework.web.jsf.DelegatingVariableResolver
),负责将表达式解析为 IOC bean。
前面已经看到了如何配置 JSF 应用程序以使用 Acegi 的 IOC bean。现在可以看看刚刚配置的三个 bean。
清单 5 显示了 Catalog
类的实现,它的实例 — 名为 catalog
— 被配置为 JSF 中的托管 bean:
从 清单 5 可以看出,Catalog
类只包含 publicData
和 privateData
属性的 getter 和 setter 方法。JSF 框架将会调用 getter 和 setter 方法,我将在下一节解释这一点。
现在看一下两个 IOC bean(publicCatalog
和 privateCatalog
)的实现,如 清单 6 所示:
清单 6. publicCatalog
和 privateCatalog
IOC bean
在 清单 6 可以看到,我在两个 IOC bean 中对实际的公共和私有数据进行了硬编码。在真实的应用程序中,这些 bean 将会从数据库读取数据。
现在已经看到了保护 JSP 托管 bean 中包装的数据所需要的所有组件和配置,下面看一下 JSF 和 Acegi 如何协作使用这些组件和配置。
当用户试图访问 清单 2 的 JSF 页面时,就会发生 图 1 所示的一系列事件。我列出了支持 Acegi URL 安全性和 JSF 应用程序中的 bean 安全性的所有事件。
图 1 所示的事件顺序如下:
- 用户访问 JSF 页面。
- Acegi 检查该用户是否有权访问该 JSF 页面。(请参阅 第 4 部分 的 “处理对受 Acegi 保护的 JSF 页面的请求” 一节。)
- 如果授权过程成功,则将控制权转到 faces servlet,由它准备提供 JSF 页面。
- 在准备期间,JSF 找到 清单 2 所示的 JSF 页面中的
catalog
bean。 - JSF 检查 清单 3 所示的配置文件,查找
catalog
bean 的定义并将其实例化。JSF 还在配置文件中检查catalog
bean 的属性。它发现catalog
bean 的publicData
和privateData
属性被映射到publicCatalog
和privateCatalog
bean,清单 3 中未将这两个 bean 配置为 JSF 托管 bean。 - JSF 使用 Spring 的
DelegatingVariableResolver
变量解析器(在 清单 4 中配置)解析publicCatalog
和privateCatalog
bean。 - JSF 使用 Acegi 调用
publicCatalog
和privateCatalog
beans 的 getter 方法获取公共和私有数据。 - Acegi 再次执行对访问 bean 的授权过程。(请参阅 第 3 部分 对 Java 对象进行这一授权过程的详细讨论。)
- 如果 Acegi 发现用户得到授权可以访问 bean,就会调用 getter 方法,获取公共和私有数据,并将数据提供给 JSF。
- JSF 调用
catalog
bean 的 setter 方法在catalog
bean 中设置公共和私有数据。 - JSF 执行其生命周期并提供 JSF 页面。
具有安全托管 bean 的 JSF-Acegi 示例应用程序
本文附带了一个名为 JSFAcegiSampleWithSecureManagedBeans
的示例应用程序(请参阅 下载)。它使用前面两节介绍的技术保护对 JSF 托管 bean 内包装的数据的访问。
要部署示例应用程序,请执行 第 1 部分 的 “部署和运行应用程序” 小节中的两个步骤。还需要从 Sun 的 JSF 网站(请参阅 参考资料)下载 jsf-1_1_01.zip 并解压。将 jsf-1.1.X.zip 中的所有文件复制到 JSFAcegiSampleWithSecureManagedBeans
应用程序的 WEB-INF/lib 文件夹中。还需要下载 cglib-full-2.0.2.jar
文件(在本系列的 第 3 部分 中用到过)并将它复制到 JSFAcegiSampleWithSecureManagedBeans
应用程序的 WEB-INF/lib
文件夹中。从浏览器访问 http://localhost:8080/JSFAcegiSampleWithSecureManagedBeans 可以调用示例应用程序。
直接在 JSF 应用程序中使用 Acegi 的 IOC bean
您已经学习了如何将 JSF 托管 bean 的属性映射到 Acegi 的 IOC bean,如何配置 DelegatingVariableResolver
以将表达式解析为 IOC bean。还看到了 JSF 和 Acegi 如何协作以保护对 bean 数据的访问。
除此之外,还能够直接在 JSF 页面中使用 IOC bean,如 清单 7 中的 JSF 页面所示:
清单 7 与 清单 2 中的 JSF 页面类似。惟一的区别在于 <outputText>
标签的 value
属性。在 清单 2 中,value
属性引用 catalog
,后者是一个 JSF 托管 bean。在 清单 7 中,value
属性直接引用 IOC bean(即 publicCatalog
和 privateCatalog
)。这意味着当用户访问 清单 7 的 JSF 页面时,JSF 直接用 Spring 的 DelegatingVariableResolver
解析 Acegi IOC。
请注意,为了解析 JSF 页面中使用的 IOC bean, DelegatingVariableResolver
的工作方式与我在讨论 图 1 时说明的方式相同。
图 2 演示了用户访问 清单 7 中的 JSF 页面时发生的事件顺序。
图 2. JSF 和 Acegi 组件协作提供带有安全 IOC bean 的 JSF 页面
- 用户访问 JSF 页面。
- Acegi 检查用户是否有权访问该 JSF 页面。
- 如果授权过程成功,则将控制权转移给 JSF,由 JSF 准备提供 JSF 页面。
- 在准备期间,JSF 找到 清单 7 的 JSF 页面中的
publicCatalog
和privateCatalog
bean。 - JSF 检查 清单 3 的配置文件,发现
publicCatalog
和privateCatalog
bean 没有在配置文件中配置为 JSF 托管 bean。JSF 使用 Spring 的DelegatingVariableResolver
解析publicCatalog
和privateCatalog
bean。 - JSF 使用 Acegi 调用
publicCatalog
和privateCatalog
bean 的 getter 方法获取公共和私有数据。 - Acegi 执行对访问 bean 的授权过程。
- 如果 Acegi 发现用户得到授权,可以访问 bean,则调用 getter 方法获取公共和私有数据,并将数据提供给 JSF。
- JSF 执行其生命周期并提供 JSF 页面。
您可以看到,清单 7 的 JSF 页面未使用任何托管 bean,所以 图 2 不包含与 JSF 托管 bean 有关的事件。
本文的源代码中还包含第二个示例应用程序,名为 JSFAcegiSampleWithIOCBeans
(请参阅 下载)。 JSFAcegiSampleWithIOCBeans
使用 清单 7 中的 JSF 页面演示了 IOC bean 在 JSF 页面中的用法。
前一节演示了能够直接在 JSF 应用程序中使用 IOC bean。如果已经有一个 JSF 应用程序,然后想用 Acegi 保护它,只需要执行以下四个配置步骤:
- 按照本系列的前三篇文章所描述的那样编写 Acegi 的配置文件。
- 按照 第 4 部分 中描述的那样编写一个 web.xml 文件。
- 按照本文的 “定义表达式解析器” 一节描述的那样在 JSF 配置文件中使用 Spring 的
DelegatingVariableResolver
。 - 在 Acegi 的配置文件而不是 JSF 的配置文件中声明 bean,重新配置要作为 IOC bean 保护的 JSF 托管 bean。
使用这项技术,可以在不用考虑安全问题的情况下开发 JSF 应用程序。开发应用程序之后,可以按照以上四个配置步骤部署 Acegi,无需编写任何 Java 安全性代码。