JSF 應用程序中使用的 bean 的可配置安全性
這個 系列 由五部分組成,介紹了 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 頁 面
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@page import="sample.CatalogBean"%>
<%@page import="org.springframework.web.context.support.WebApplicationContextU tils" %>
<%@page import="org.springframework.web.context.WebApplicationContext" % >
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" % >
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" % >
<html>
<head>
<title>Acegi simple method security application: TEST PAGE</title>
</head>
<body>
<f:view>
<h2>
<h:outputText value="Protected Resource 1:"/>
</h2>
<%
try {
WebApplicationContext webApplicationContext =
WebApplicationContextUtils.getWebApplicationContext (
this.getServletConfig().getServletContext());
CatalogBean privateCatalog = (CatalogBean)
webApplicationContext.getBean("privateCatalog");
String privateData = catalog.getData();
request.setAttribute("privateData", privateData);
}
catch (Exception e) { }
%>
<h3>
<h:outputText value="#{privateData}"/>
</h3>
</f:view>
</body>
</html>
可以看到,清單 1 使用名為 WebApplicationContextUtils 的類提取 Web 應用程序上下文的實例。WebApplicationContextUtils 是 Spring 提供的一個 工具類。
在得到 Web 應用程序上下文之後,能夠調用它的 getBean() 方法得到在 Acegi 配置文件中配置的任何 bean。然後可以調用該 bean 的 getter 方法, 並將 getter 方法返回的數據以參數的形式存儲在 servlet 請求對象中。這些 步驟允許 清單 1 中的 <outputText> 標簽向用戶提供數據。
不是最佳方式
像 清單 1 那樣直接管理 bean 數據雖然簡單,但並不可取。這個方法違反 了 JSF 的模型-視圖-控制器(MVC)架構,MVC 架構要求使用模型 bean 保存應 用程序數據。所以最好不要在 JSF 應用程序中使用這種策略,除非在非常簡單 的情況下。
Bean 依賴關系
Spring 的 IOC 框架通過表示 bean 之間的依賴關系,提供了一種管理模型 bean 的有用方式。我在 第 1 部分的 “架構和組件” 小節解釋了 IOC 中的 bean 依賴關系概念。
JSF 提供了管理應用程序模型 bean 的豐富功能。這類 bean — 稱為托管 bean — 被應用於大多數 JSF 應用程序,所以大多數實際的 JSF 應用程序都需 要保護托管 bean。
本文余下部分將討論在 JSF 應用程序中使用安全 bean 的兩個策略:
使用 Acegi 保護 JSF 托管 bean
使用直接由 Acegi 在 JSF 標簽中保護的 反轉控制(IOC)bean
保護 JSF 托管 bean
請看 清單 2 所示的 JSF 頁面,其中使用了一個名為 catalog 的 JSF 托管 bean:
清單 2. 使用托管 bean 的簡單 JSF 頁面
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" % >
<html>
<head>
<title>JSF Acegi simple method security application: TEST PAGE</title>
</head>
<body>
<f:view>
<h2>
<h:outputText value="Protected Resource 1:"/>
</h2>
</br>
<h3>
<h:outputText value="#{catalog.publicData}"/>
</br>
<h:outputText value="#{catalog.privateData}"/>
</h3>
</f:view>
</body>
</html>
清單 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 編目應用程序的公共和私有數據的包裝器。
定義托管 bean
清單 3 演示了如何定義 catalog bean,以便將它的 publicData 和 privateData 屬性分別映射到 Acegi 的 publicCatalog 和 privateCatalog bean:
清單 3. 將 catalog 的屬性映射到 Acegi 的 bean
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
<managed-bean>
<managed-bean-name>catalog</managed-bean-name>
<managed-bean-class>sample.Catalog</managed-bean- class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>publicData</property-name>
<value>#{publicCatalog.data}</value>
</managed-property>
<managed-property>
<property-name>privateData</property-name>
<value>#{privateCatalog.data}</value>
</managed-property>
</managed-bean>
<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application>
</faces-config>
清單 3 實際上演示了 JSF 的一個配置文件。它的根標簽是 <faces- config>,這是大多數 JSF 程序員都熟悉的標簽。根 <faces-config> 標簽包含兩個子標簽,名為 <managed-bean> 和 <application>。 現在我要詳細解釋這兩個標簽。
在 faces 配置文件中聲明 bean 屬性
清單 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> 標簽
<faces-config>
..........
<application>
<variable-resolver>
org.springframework.web.jsf.DelegatingVariableResolver
</variable-resolver>
</application>
</faces-config>
清單 4 中的 <application> 標簽只包含一個子標簽,名為 <variable-resolver>,用於為 JSF 應用程序配置外部解析器。 <variable-resolver> 標簽包裝了 Spring 解析器類的名稱 (org.springframework.web.jsf.DelegatingVariableResolver),負責將表達 式解析為 IOC bean。
實現 JSF 和 IOC bean
前面已經看到了如何配置 JSF 應用程序以使用 Acegi 的 IOC bean。現在可 以看看剛剛配置的三個 bean。
清單 5 顯示了 Catalog 類的實現,它的實例 — 名為 catalog — 被配置 為 JSF 中的托管 bean:
清單 5. Catalog 類
package sample;
public class Catalog
{
private String publicData = null;
private String privateData = null;
public Catalog () {
}
public void setPublicData(String publicData) {
this.publicData = publicData;
}
public void setPrivateData(String privateData) {
this.privateData = privateData;
}
public String getPublicData() {
return publicData;
}
public String getPrivateData() {
return privateData;
}
}//Catalog
從 清單 5 可以看出,Catalog 類只包含 publicData 和 privateData 屬性 的 getter 和 setter 方法。JSF 框架將會調用 getter 和 setter 方法,我將 在下一節解釋這一點。
現在看一下兩個 IOC bean(publicCatalog 和 privateCatalog)的實現, 如 清單 6 所示:
清單 6. publicCatalog 和 privateCatalog IOC bean
//PublicCatalog
package sample;
public class PublicCatalog implements CatalogBean {
public PublicCatalog () { }
public String getData() {
return "This is public catalog data";
}
}
//PrivateCatalog
package sample;
public class PrivateCatalog implements CatalogBean {
public PrivateCatalog () { }
public String getData() {
return "This is private catalog data";
}
}
在 清單 6 可以看到,我在兩個 IOC bean 中對實際的公共和私有數據進行 了硬編碼。在真實的應用程序中,這些 bean 將會從數據庫讀取數據。
現在已經看到了保護 JSP 托管 bean 中包裝的數據所需要的所有組件和配置 ,下面看一下 JSF 和 Acegi 如何協作使用這些組件和配置。
JSF 和 Acegi 協作保護托管 bean
當用戶試圖訪問 清單 2 的 JSF 頁面時,就會發生 圖 1 所示的一系列事件 。我列出了支持 Acegi URL 安全性和 JSF 應用程序中的 bean 安全性的所有事 件。
圖 1. JSF 和 Acegi 組件協作
圖 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. 直接在 JSF 頁面中使用 IOC bean
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" % >
<html>
<head>
<title>JSF Acegi simple method security application: TEST PAGE</title>
</head>
<body>
<f:view>
<h2>
<h:outputText value="Protected Resource 1:"/>
</h2>
</br>
<h3>
<h:outputText value="#{publicCatalog.data}"/>
</br>
<h:outputText value="#{privateCatalog.data}"/>
</h3>
</f:view>
</body>
</html>
清單 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 頁面
圖 2 顯示的事件順序與 圖 1 的順序稍微有點不同:
用戶訪問 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 頁面中的用法。
使用 Acegi 保護現有 JSF 應用程序
前一節演示了能夠直接在 JSF 應用程序中使用 IOC bean。如果已經有一個 JSF 應用程序,然後想用 Acegi 保護它,只需要執行以下四個配置步驟:
按照本系列的前三篇文章所描述的那樣編寫 Acegi 的配置文件。
按照 第 4 部分 中描述的那樣編寫一個 web.xml 文件。
按照本文的 “定義表達式解析器” 一節描述的那樣在 JSF 配置文件中使用 Spring 的 DelegatingVariableResolver。
在 Acegi 的配置文件而不是 JSF 的配置文件中聲明 bean,重新配置要作為 IOC bean 保護的 JSF 托管 bean。
使用這項技術,可以在不用考慮安全問題的情況下開發 JSF 應用程序。開發 應用程序之後,可以按照以上四個配置步驟部署 Acegi,無需編寫任何 Java 安 全性代碼。
結束語
在本系列的文章中,學習了如何使用 Acegi 保護 Java 應用程序。您現在掌 握了如何加強基於 URL 的安全性和基於方法的安全性。還學習了如何設計訪問 控制策略和在目錄服務其中托管這些策略,以及如何根據托管的訪問控制策略執 行身份驗證和授權決策。在本文和前一篇文章中,主要關注了 JSF 應用程序, 學習了如何在不編寫任何 Java 安全性代碼的情況下保護 JSF 應用程序。
本文配套源碼