使用 Acegi 保护 Java 应用程序
第 1 部分: 架構概覽和安全過濾器
Acegi Security System 是一種功能強大并易于使用的替代性方案,使您不必再為 Java 企業應用程序編寫大量的安全代碼。雖然它專門針對使用 Spring 框架編寫的應用程序,但是任何類型的 Java 應用程序都沒有理由不去使用 Acegi。這份共分三部分的系列文章詳細介紹了 Acegi,并展示了如何使用它保護簡單的企業應用程序以及更復雜的應用程序。
本系列首先介紹企業應用程序中常見的安全問題,并說明 Acegi 如何解決這些問題。您將了解 Acegi 的架構模型及其安全過濾器,后者包含了在保護應用程序中將用到的大多數功能。您還將了解到各個過濾器如何單獨進行工作,如何將它們組合起來,以及過濾器如何在一個企業安全實現中將各種功能從頭到尾地鏈接起來。本文最后通過一個樣例應用程序演示了基于 URL 安全系統的 Acegi 實現。本系列后續兩篇文章將探究 Acegi 的一些更高級的應用,包括如何設計和托管訪問控制策略,然后如何去配置 Acegi 以使用這些策略。
您必須?下載 Acegi,這樣才能編譯本文的示例代碼并運行本文的樣例應用程序。還必須有作為工作站的一部分運行的 Tomcat 服務器。
企業應用程序安全性
由于企業內容管理(ECM)應用程序管理存儲在不同類型數據源(如文件系統、關系數據庫和目錄服務)中的企業內容的編寫和處理,ECM 安全性要求對這些數據源的訪問進行控制。比如,一個 ECM 應用程序可能會控制被授權讀取、編輯或刪除數據的對象,而這些數據和制造業企業的設計、市場營銷、生產以及質量控制有關。
在一個 ECM 安全場景中,比較常見的就是通過對企業資源定位符(或網絡地址)應用安全性,從而實現訪問控制。這種簡單的安全模型被稱為統一資源定位符?或 URL 安全性。正如我在本文后面(以及本系列后續文章)所演示的一樣,Acegi 為實現 URL 安全性提供了全面的特性。
然而,在很多企業場景中,URL 安全性還遠遠不夠。比如,假設一個 PDF 文檔包含某個制造業公司生產的特殊產品的數據。文檔的一部分包含了將由該公司的設計部門編輯和更新的設計數據。另一部分包含了生產經理將使用的生產數據。對于諸如此類的場景,需要實現更加細粒度的安全性,對文檔的不同部分應用不同的訪問權限。
本文介紹了 Acegi 為實現 URL 安全性而提供的各種功能。本系列的下一篇文章將演示此框架的基于方法的安全性,它提供了對企業數據訪問的更細粒度的控制。
回頁首
Acegi Security System
Acegi Security System 使用安全過濾器來提供企業應用程序的身份驗證和授權服務。該框架提供了不同類型的過濾器,可以根據應用程序的需求進行配置。您將在本文后面了解到?安全過濾器的不同類型;現在,只需注意可以為如下任務配置 Acegi 安全過濾器:
正如這個列表顯示的那樣,Acegi 的安全過濾器允許您執行保護企業應用程序所需的幾乎任何事情。
回頁首
架構和組件
對 Acegi 了解越多,使用起來就越簡單。這一節介紹 Acegi 的組件;接下來您將了解該框架如何使用反轉控制(IOC)和 XML 配置文件來組合組件并表示它們的依賴關系。
四大組件
Acegi Security System 由四種主要類型的組件組成:過濾器、管理器、提供者和處理程序。
過濾器反轉控制
Acegi 的組件通過彼此之間的依賴來對企業應用程序進行保護。比如,一個身份驗證處理過濾器需要一個身份驗證管理器選擇一個合適的身份驗證提供者。這就是說您必須能夠表示和管理 Acegi 組件的依賴關系。
IOC 實現通常用于管理 Java 組件之間的依賴關系。IOC 提供了兩個重要的特性:
XML 配置文件
Acegi 使用 Spring 框架(請參見?參考資料)附帶的流行開源 IOC 實現來管理其組件。Spring 需要您編寫一個 XML 配置文件來表示組件的依賴關系,如清單 1 所示:
清單 1. Spring 配置文件的結構
<beans><bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"><property name="filterInvocationDefinitionSource"><value> value here </value></property></bean><bean id="authenticationProcessingFilter"class="org.acegisecurity.ui.webapp.AuthenticationProcessingFitler"><property name="authenticationManager" ref="authManager"/><!-- Other properties --></bean><bean id="authManager"class="org.acegisecurity.providers.ProviderManager"><property name="providers"><!-- List of providers here --></property></bean><!-- Other bean tags --> </beans>如您所見,Acegi 使用的 Spring XML 配置文件包含一個?<beans>?標記,它封裝了一些其他的?<bean>?標記。所有的 Acegi 組件(即過濾器、管理器、提供者等)實際上都是 JavaBean。XML 配置文件中的每個?<bean>?標記都代表一個 Acegi 組件。
進一步解釋 XML 配置文件
首先將注意到的是每個?<bean>?標記都包含一個?class?屬性,這個屬性標識組件所使用的類。<bean>?標記還具有一個?id?屬性,它標識作為 Acegi 組件工作的實例(Java 對象)。
比方說,清單 1?的第一個?<bean>?標記標識了名為?filterChainProxy?的組件實例,它是名為?org.acegisecurity.util.FilterChainProxy的類的實例。
使用?<bean>?標記的子標記來表示 bean 的依賴關系。比如,注意第一個?<bean>?標記的?<property>?子標記。<property>?子標記定義了<bean>?標記依賴的其他 bean 或值。
所以在?清單 1?中,第一個?<bean>?標記的?<property>?子標記具有一個 name 屬性和一個?<value>?子標記,分別定義了這個 bean 依賴的屬性的名稱和值。
同樣,清單 1?中的第二個和第三個?<bean>?標記定義了一個過濾器 bean 依賴于一個管理器 bean。第二個?<bean>?標記表示過濾器 bean,而第三個?<bean>?標記表示管理器 bean。
過濾器的?<bean>?標記包含一個?<property>?子標記,該子標記具有兩個屬性:name?和?ref。name?屬性定義了過濾器 bean 的屬性,而?ref?屬性引用了管理器 bean 的實例(名稱)。
下一節將展示如何在 XML 配置文件中配置 Acegi 過濾器。在本文后面的內容中,您將在一個樣例 Acegi 應用程序中使用過濾器。
回頁首
安全過濾器
正如我前面提到的一樣,Acegi 使用安全過濾器為企業應用程序提供身份驗證和授權服務。您可以根據應用程序的需要使用和配置不同類型的過濾器。這一節將介紹五種最重要的 Acegi 安全過濾器。
Session Integration Filter
Acegi 的?Session Integration Filter(SIF)通常是您將要配置的第一個過濾器。SIF 創建了一個安全上下文對象,這是一個與安全相關的信息的占位符。其他 Acegi 過濾器將安全信息保存在安全上下文中,也會使用安全上下文中可用的安全信息。
SIF 創建安全上下文并調用過濾器鏈中的其他過濾器。然后其他過濾器檢索安全上下文并對其進行更改。比如,Authentication Processing Filter(我將稍后進行介紹)將用戶信息(如用戶名、密碼和電子郵件地址)存儲在安全上下文中。
當所有的處理程序完成處理后,SIF 檢查安全上下文是否更新。如果任何一個過濾器對安全上下文做出了更改,SIF 將把更改保存到服務器端的會話對象中。如果安全上下文中沒有發現任何更改,那么 SIF 將刪除它。
在 XML 配置文件中對 SIF 進行了配置,如清單 2 所示:
清單 2. 配置 SIF
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>Authentication Processing Filter
Acegi 使用?Authentication Processing Filter(APF)進行身份驗證。APF 使用一個身份驗證(或登錄)表單,用戶在其中輸入用戶名和密碼,并觸發身份驗證。
APF 執行所有的后端身份驗證處理任務,比如從客戶機請求中提取用戶名和密碼,從后端用戶庫中讀取用戶參數,以及使用這些信息對用戶進行身份驗證。
在配置 APF 時,您必須提供如下參數:
- Authentication manager?指定了用來管理身份驗證提供者的身份驗證管理器。
- Filter processes URL?指定了客戶在登錄窗口中按下?Sign In?按鈕時要訪問的 URL。收到這個 URL 的請求后,Acegi 立即調用 APF。
- Default target URL?指定了成功進行身份驗證和授權后呈現給用戶的頁面。
- Authentication failure URL?指定了身份驗證失敗情況下用戶看到的頁面。
APF 從用戶的請求對象中得到用戶名、密碼和其他信息。它將這些信息傳送給身份驗證管理器。身份驗證管理器使用適當的提供者從后端用戶庫中讀取詳細的用戶信息(如用戶名、密碼、電子郵件地址和用戶訪問權利或特權),對用戶進行身份驗證,并將信息存儲在一個Authentication?對象中。
最后,APF 將?Authentication?對象保存在 SIF 之前創建的安全上下文中。存儲在安全上下文中的?Authentication?對象將用于做出授權決策。
APF 的配置如清單 3 所示:
清單 3. 配置 APF
<bean id="authenticationProcessingFilter"class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"><property name="authenticationManager"ref="authenticationManager" /><property name="filterProcessesUrl"value="/j_acegi_security_check" /><property name="defaultTargetUrl"value="/protected/protected1.jsp" /><property name="authenticationFailureUrl"value="/login.jsp?login_error=1" /> </bean>可以從這段代碼中看到,APF 依賴于上面討論的這四個參數。每個參數都是作為清單 3 所示的?<property>?標記配置的。
Logout Processing Filter
Acegi 使用一個?Logout Processing Filer(LPF)管理注銷處理。當客戶機發來注銷請求時,將使用 LPF 進行處理。它標識了來自由客戶機所調用 URL 的注銷請求。
LPF 的配置如清單 4 所示:
清單 4. 配置 LPF
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter"><constructor-arg value="/logoutSuccess.jsp"/><constructor-arg><list><bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/></list></constructor-arg> </bean>可以看到 LPF 在其構造方法中包含兩個參數:注銷成功 URL(/logoutSuccess.jsp)和處理程序列表。注銷成功 URL 用來在注銷過程完成后重定向客戶機。處理程序執行實際的注銷過程;我在這里只配置了一個處理程序,因為只需一個處理程序就可以使 HTTP 會話變為無效。我將在本系列下一篇文章中討論更多的處理程序。
Exception Translation Filter
Exception Translation Filter(ETF)處理身份驗證和授權過程中的異常情況,比如授權失敗。在這些異常情況中,ETF 將決定如何進行操作。
比如,如果一個沒有進行身份驗證的用戶試圖訪問受保護的資源,ETF 將顯示一個登錄頁面要求用戶進行身份驗證。類似地,在授權失敗的情況下,可以配置 ETF 來呈現一個 Access Denied 頁面。
ETF 的配置如清單 5 所示:
清單 5. 配置 ETF
<bean id="exceptionTranslationFilter"class="org.acegisecurity.ui.ExceptionTranslationFilter"><property name="authenticationEntryPoint"><bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"><property name="loginFormUrl" value="/login.jsp" /></bean></property><property name="accessDeniedHandler"><bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"><property name="errorPage" value="/accessDenied.jsp" /></bean></property> </bean>正如清單 5 所示,ETF 包含兩個參數,名為?authenticationEntryPoint?和?accessDeniedHandler。authenticationEntryPoint?屬性指定登錄頁面,而?accessDeniedHandler?指定 Access Denied 頁面。
攔截過濾器
Acegi 的攔截過濾器?用于做出授權決策。您需要在 APF 成功執行身份驗證后對攔截過濾器進行配置,以使其發揮作用。攔截器使用應用程序的訪問控制策略來做出授權決定。
本系列的下一篇文章將展示如何設計訪問控制策略,如何將它們托管在目錄服務中,以及如何配置 Acegi 以讀取您的訪問控制策略。但是,目前我將繼續向您展示如何使用 Acegi 配置一個簡單的訪問控制策略。在本文后面的部分,您將看到使用簡單的訪問控制策略構建一個樣例應用程序。
配置簡單的訪問控制策略可分為兩個步驟:
步驟 1. 編寫簡單的訪問控制策略
首先看一下?清單 6,它展示了如何定義一個用戶及其用戶角色:
清單 6. 為用戶定義簡單的訪問控制策略
alice=123,ROLE_HEAD_OF_ENGINEERING清單 6 所示的訪問控制策略定義了用戶名?alice,它的密碼是?123,角色是?ROLE_HEAD_OF_ENGINEERING。(下一節將說明如何在文件中定義任意數量的用戶及其用戶角色,然后配置攔截過濾器以使用這些文件。)
步驟 2. 配置 Acegi 的攔截過濾器
攔截過濾器使用三個組件來做出授權決策,我在清單 7 中對其進行了配置:
清單 7. 配置攔截過濾器
<bean id="filterInvocationInterceptor"class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"><property name="authenticationManager" ref="authenticationManager" /><property name="accessDecisionManager" ref="accessDecisionManager" /><property name="objectDefinitionSource"><value>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISONPATTERN_TYPE_APACHE_ANT/protected/**=ROLE_HEAD_OF_ENGINEERING/**=IS_AUTHENTICATED_ANONYMOUSLY</value></property><!-- More properties of the interceptor filter --> </bean>如清單 7 所示,配置所需的三個組件是?authenticationManager、accessDecisionManager、objectDefinitionSource:
- authenticationManager?組件與我在介紹?Authentication Processing Filter?時討論過的身份驗證管理器相同。攔截過濾器可以在授權的過程中使用?authenticationManager?重新對客戶機進行身份驗證。
- accessDecisionManager?組件管理授權過程,這部分內容將在本系列的下篇文章中詳細討論。
- objectDefinitionSource?組件包含對應于將要發生的授權的訪問控制定義。例如,清單 7?中的?objectDefinitionSource?屬性值包含兩個 URL(/protected/*?和?/*)。其值定義了這些 URL 的角色。/protected/*?URL 的角色是?ROLE_HEAD_OF_ENGINEERING。您可以根據應用程序的需要定義任何角色。
回想一下?清單 6,您為用戶名?alice?定義了?ROLE_HEAD_OF_ENGINEERING。這就是說?alice?將能夠訪問?/protected/*?URL。
回頁首
過濾器工作原理
正如您已經了解到的一樣,Acegi 的組件彼此依賴,從而對您的應用程序進行保護。在本文后面的部分,您將看到如何對 Acegi 進行配置,從而按照特定的順序應用安全過濾器,因此需要創建過濾器鏈。出于這個目的,Acegi 保存了一個過濾器鏈對象,它封裝了為保護應用程序而配置了的所有過濾器。圖 1 展示了 Acegi 過濾器鏈的生命周期,該周期從客戶機向您的應用程序發送 HTTP 請求開始。(圖 1 顯示了服務于瀏覽器客戶機的容器。)
圖 1. 托管 Acegi 過濾器鏈以安全地為瀏覽器客戶機服務的容器
下面的步驟描述了過濾器鏈的生命周期:
為幫助您進一步理解 Acegi 過濾器,我將詳細探討其中兩個過濾器的操作:Session Integration Filter 和 Authentication Processing Filter。
SIF 如何創建一個安全上下文
圖 2 展示了 SIF 創建安全上下文所涉及到的步驟:
圖 2. SIF 創建安全上下文
現在詳細地考慮下面這些步驟:
APF 如何對用戶進行身份驗證
圖 3 展示了 APF 對用戶進行身份驗證所涉及到的步驟:
圖 3. APF 對用戶進行身份驗證
現在仔細考慮以下這些步驟:
回頁首
一個簡單的 Acegi 應用程序
在本文中,您已經了解了很多關于 Acegi 的知識,所以現在看一下利用您目前學到的知識能做些什么,從而結束本文。對于這個簡單的演示,我設計了一個樣例應用程序(參見?下載),并對 Acegi 進行了配置以保護它的一些資源。
樣例應用程序包含 5 個 JSP 頁面:index.jsp、protected1.jsp、protected2.jsp、login.jsp 和 accessDenied.jsp。
index.jsp 是應用程序的歡迎頁面。它向用戶顯示了三個超鏈接,如圖 4 所示:
圖 4. 樣例應用程序的歡迎頁面:
圖 4 所示的鏈接中,其中兩個鏈接指向了被保護的資源(protected1.jsp 和 protected2.jsp),第三個鏈接指向登錄頁面(login.jsp)。只有在 Acegi 發現用戶沒有被授權訪問受保護的資源時,才會顯示 accessDenied.jsp 頁面。
如果用戶試圖訪問任何受保護的頁面,樣例應用程序將顯示登錄頁面。當用戶使用登錄頁面進入后,應用程序將自動重定向到被請求的受保護資源。
用戶可以通過單擊歡迎頁面中的第三個鏈接直接請求登錄頁面。這種情況下,應用程序顯示用戶可以進入系統的登錄頁面。進入系統以后,應用程序將用戶重定向到 protected1.jsp,它是用戶進入系統而沒有請求特定的受保護資源時顯示的默認資源。
配置樣例應用程序
為本文下載的源代碼包含一個名為 acegi-config.xml 的 XML 配置文件,它包含 Acegi 過濾器的配置。根據?安全過濾器的討論?中的示例,您應該很熟悉這些配置。
我還為樣例應用程序編寫了一個?web.xml?文件,如清單 8 所示:
清單 8. 樣例應用程序的 web.xml 文件
<web-app><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/acegi-config.xml</param-value></context-param><filter><filter-name>Acegi Filter Chain Proxy</filter-name><filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class><init-param><param-name>targetClass</param-name><param-value>org.acegisecurity.util.FilterChainProxy</param-value></init-param></filter><filter-mapping><filter-name>Acegi Filter Chain Proxy</filter-name><url-pattern>/*</url-pattern></filter-mapping><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> </web-app>web.xml 文件配置如下:
- acegi-config.xml?文件的 URL 位于?<context-param>?標記中。
- Acegi 過濾器鏈代理類的名稱位于?<filter>?標記中。
- URL 到 Acegi 過濾器鏈代理的映射在?<filter-mapping>?標記中。注意:您可以簡單地將應用程序的所有 URL(/*)映射到 Acegi 過濾器鏈代理。Acegi 將對映射到 Acegi 過濾器鏈代理上的所有 URL 應用安全性。
- 應用程序上下文加載程序位于?<listener>?標記中,它將加載 Spring 的 IOC 框架。
部署并運行應用程序
部署并運行樣例應用程序非常的簡單。只需要完成兩件事情:
現在,您已經為運行樣例應用程序做好準備了。啟動 Tomcat 并將瀏覽器指向?http://localhost:8080/acegisample/。
您將看到?圖 4?所示的歡迎頁面,但是此時顯示的頁面是真實的。請繼續運行程序,并查看在嘗試訪問歡迎頁面顯示的不同鏈接時會發生什么狀況。
回頁首
結束語
在使用 Acegi 保護 Java 應用程序?系列的第一篇文章中,您了解了 Acegi 安全系統的特性、架構和組件,學習了大量有關 Acegi 安全過濾器的知識,這些過濾器被集成到 Acegi 的安全框架中。您還學習了如何使用 XML 配置文件配置組件依賴關系,并查看了 Acegi 的安全過濾器在樣例程序中工作的情形,該應用程序可以實現基于 URL 的安全性。
本文所述的安全技術非常的簡單,所以 Acegi 使用這些技術實現安全性。本系列的下一文章將開始介紹 Acegi 的一些較為高級的應用,首先是編寫訪問控制協議并將其存儲到目錄服務中。您還將了解到如何配置 Acegi,使它與目錄服務交互,從而實現您的訪問控制策略。
第 2 部分: 使用 LDAP 目錄服務器
這期共分三部分的系列文章介紹了如何使用 Acegi 安全系統保護 Java 企業應用程序。在?本系列第一篇文章?中,我介紹了 Acegi 并解釋了如何使用安全過濾器實現一個簡單的基于 URL 的安全系統。在第二篇文章中,我將討論 Acegi 的更加高級的應用,首先我將編寫一個訪問控制策略并將其存儲在 ApacheDS 中,ApacheDS 是一個開源的 LDAP 目錄服務器。我還將展示配置 Acegi 的方法,使它能夠與目錄服務器交互并實現您的訪問控制策略。本文的結尾提供了一個示例應用程序,它使用 ApacheDS 和 Acegi 實現了一個安全的訪問控制策略。
實現訪問控制策略通常包含兩個步驟:
Acegi 將減輕代碼編寫的工作,因此在這篇文章中,我將展示如何將用戶和用戶角色信息存儲到 ApacheDS 中,然后實現這些信息的訪問控制策略。在該系列的最后一篇文章中,我將展示如何配置 Acegi,實現對 Java 類的安全訪問。
您可以在本文的任何位置?下載樣例應用程序。參見?參考資料?下載 Acegi、Tomcat 和 ApacheDS,您需要使用它們運行樣例代碼和示例應用程序。
LDAP 基礎知識
輕量級目錄訪問協議(Lightweight Directory Access Protocol,LDAP)可能是最流行的一種定義數據格式的協議,它針對常見的目錄操作,例如對存儲在目錄服務器中的信息執行的讀取、編輯、搜索和刪除操作。本節將簡要解釋為什么目錄服務器是屬性文件存儲安全信息的首選,并展示如何在 LDAP 目錄中組織和托管用戶信息。
為什么要使用目錄服務器?
本系列第一部分向您介紹了一種簡單的方法,可以將用戶信息以屬性文件的形式保存起來(參見?第 1 部分,清單 6)。屬性文件以文本格式保存用戶名、密碼和用戶角色。對于大多數真實應用程序而言,使用屬性文件存儲安全信息遠遠不夠。各種各樣的理由表明,目錄服務器通常都是更好的選擇。其中一個原因是,真實的企業應用程序可以被大量用戶訪問 —— 通常是幾千名用戶,如果應用程序將其部分功能公開給用戶和供應商時更是如此。頻繁搜索文本文件中隨意存儲的信息,這樣做的效率并不高,但是目錄服務器對這類搜索進行了優化。
第 1 部分的清單 6?中的屬性文件演示了另一個原因,該文件組合了用戶和角色。在真實的訪問控制應用程序中,您通常都需要分別定義和維護用戶和角色信息,這樣做可以簡化用戶庫的維護。目錄服務器為更改或更新用戶信息提供了極大的靈活性,例如,反映職位升遷或新聘用人員。參見?參考資料?以了解更多關于目錄服務器的使用及其優點的信息。
LDAP 目錄設置
如果希望將用戶信息存儲在一個 LDAP 目錄中,您需要理解一些有關目錄設置的內容。本文并沒有提供對 LDAP 的完整介紹(參見?參考資料),而是介紹了一些在嘗試結合使用 Acegi 和 LDAP 目錄之前需要了解的基本概念。
LDAP 目錄以節點樹的形式存儲信息,如圖 1 所示:
圖 1. LDAP 目錄的樹狀結構
在圖 1 中,根節點的名稱為?org。根節點可以封裝與不同企業有關的數據。例如,本系列第 1 部分開發的制造業企業被顯示為?org?節點的直接子節點。該制造業企業具有兩個名為?departments?和?partners?的子節點。
partners?子節點封裝了不同類型的合作伙伴。圖 1 所示的三個分別為?customers、employees?和?suppliers。注意,這三種類型的合作伙伴其行為與企業系統用戶一樣。每一種類型的用戶所扮演的業務角色不同,因此訪問系統的權利也不同。
類似地,departments?節點包含該制造業企業的不同部門的數據 —— 例如?engineering?和?marketing?字節點。每個部門節點還包含一組或多組用戶。在?圖 1?中,engineers?組是?engineering?部門的子節點。
假設每個部門的子節點表示一組用戶。因此,部門節點的子節點具有不同的用戶成員。例如,設計部門的所有工程師都是?engineering?部門內engineers?組的成員。
最后,注意?圖 1?中?departments?節點的最后一個子節點。specialUser?是一名用戶,而非一組用戶。在目錄設置中,像?alice?和?bob?之類的用戶一般都包含在?partners?節點中。我將這個特殊用戶包含在?departments?節點中,以此證明 Acegi 允許用戶位于 LADP 目錄中任何地點的靈活性。稍后在本文中,您將了解如何配置 Acegi 以應用?specialUser。
使用專有名稱
LDAP 使用專有名稱(DN)的概念來識別 LDAP 樹上特定的節點。每個節點具有惟一的 DN,它包含該節點完整的層次結構信息。例如,圖 2 展示了圖 1 中的一些節點的 DN:
圖 2. LDAP 目錄節點的專有名稱
首先,注意圖 2 中根節點的 DN。它的 DN 為?dc=org,這是與?org?根節點相關的屬性值對。每個節點都有若干個與之相關的屬性。dc?屬性代表 “domain component” 并由 LDAP RFC 2256 定義(參見?參考資料?中有關官方 RFC 文檔的鏈接),LDAP 目錄中的根節點通常表示為一個域組件。
每個 LDAP 屬性是由 RFC 定義的。LDAP 允許使用多個屬性創建一個 DN,但是本文的示例只使用了以下 4 個屬性:
- dc(域組件)
- o(組織)
- ou(組織單元)
- uid(用戶 ID)
示例使用?dc?表示域,用?o?表示組織名稱,ou?表示組織的不同單元,而?uid?表示用戶。
由于?org?是根節點,其 DN 只需指定自身的名稱(dc=org)。比較一下,manufacturingEnterprise?節點的 DN 是o=manufacturingEnterprise,dc=org。當向下移動節點樹時,每個父節點的 DN 被包含在其子節點的 DN 中。
將屬性分組
LDAP 將相關的屬性類型分到對象類中。例如,名為?organizationalPerson?的對象類所包含的屬性定義了在組織內工作的人員(例如,職稱、常用名、郵寄地址等等)。
對象類使用繼承特性,這意味著 LDAP 定義了基類來保存常用屬性。然后子類再對基類進行擴展,使用其定義的屬性。LDAP 目錄中的單個節點可以使用若干個對象類:本文的示例使用了以下幾個對象類:
- top?對象類是 LDAP 中所有對象類的基類。
- 當其他對象類都不適合某個對象時,將使用?domain?對象類。它定義了一組屬性,任何一個屬性都可以用來指定一個對象。其?dc?屬性是強制性的。
- organization?對象類表示組織節點,例如?圖 2?中的?manufacturingEnterprise。
- organizationalUnit?對象類表示組織內的單元,例如?圖 1?中的?departments?節點及其子節點。
- groupOfNames?對象類表示一組名稱,例如某部門職員的名稱。它具有一個?member?屬性,該屬性包含一個用戶列表。圖 1?中所有的組節點(例如?engineers?節點)使用?member?屬性指定該組的成員。而且,示例使用?groupOfNames?對象類的?ou(組織單元)屬性指定組用戶的業務角色。
- organizationalPerson?對象類表示組織內某個職員(例如?圖 1?中的?alice?節點)。
回頁首
使用 LDAP 服務器
在真實的應用程序中,通常將有關系統用戶的大量信息托管在一個 LDAP 目錄中。例如,將存儲每個用戶的用戶名、密碼、職稱、聯系方式和工資信息。為簡單起見,下面的例子將只向您展示如何保存用戶名和密碼。
如前所述,示例使用 ApacheDS(一種開源的 LDAP 目錄服務器)演示了 Acegi 是如何使用 LDAP 目錄的。示例還使用了一個開源的 LDAP 客戶機(名為 JXplorer)執行簡單的目錄操作,例如將信息托管在 ApacheDS 上。參見?參考資料?以下載 ApacheDS、JXplorer 并了解更多有關兩者協作的信息。
在 ApacheDS 創建一個根節點
要創建?圖 1?所示的節點樹,必須首先在 ApacheDS 中創建一個根節點?org。ApacheDS 為此提供了一個 XML 配置文件。XML 配置文件定義了一組可進行配置的 bean,從而根據應用程序的需求定制目錄服務器的行為。本文只解釋創建根節點所需的配置。
可以在 ApacheDS 安裝中的?conf?文件夾找到名為?server.xml?的 XML 配置文件。打開文件后,會發現很多 bean 配置類似于 Acegi 的過濾器配置。查找名為?examplePartitionsConfiguration?的 bean。該 bean 控制 ApacheDS 上的分區。當創建新的根節點時,實際上將在 LDAP 目錄上創建一個新的分區。
編輯?examplePartitionConfiguration?bean 以創建?org?根節點,如清單 1 所示:
清單 1. 編輯模式的 examplePartitionConfiguration bean 配置
<bean id="examplePartitionConfiguration" class= "org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration" ><property name="suffix"><value>dc=org</value></property><property name="contextEntry"> <value> objectClass: top objectClass: domain dc: org </value> </property><!-- Other properties of the examplePartitionConfiguration bean, which you don't need to edit. --></bean>清單 1 編輯了?examplePartitionConfiguration?bean 的兩個屬性:
- 一個屬性名為?suffix,它定義根條目的 DN。
- 另一個屬性名為?contextEntry,定義?org?節點將使用的對象類。注意,org?根節點使用兩個對象類:top?和?domain。
本文的?源代碼下載?部分包含了編輯模式的 server.xml 文件。如果希望繼續學習本示例,請將 server.xml 文件從源代碼中復制到您的 ApacheDS 安裝目錄中的正確位置,即?conf?文件夾。
圖 3 所示的屏幕截圖展示了在 ApacheDS 中創建根節點后,JXplorer 是如何顯示該根節點的:
圖 3. JXplorer 顯示根節點
填充服務器
設置 LDAP 服務器的下一步是使用用戶和組信息填充服務器。您可以使用 JXplorer 在 ApacheDS 中逐個創建節點,但是使用 LDAP Data Interchange Format (LDIF) 填充服務器會更加方便。LDIF 是可被大多數 LDAP 實現識別的常見格式。developerWorks 文章很好地介紹了 LDIF 文件的內容,因此本文將不再做詳細說明。(參見?參考資料?中有關 LDIF 的詳細資料。)
您可以在?源代碼下載?部分查看 LDIF 文件,它表示?圖 1?所示的用戶和部門。您可以使用 JXplorer 將 LDIF 文件導入到 ApacheDS。要導入 LDIF 文件,在 JXplorer 中使用?LDIF?菜單,如圖 4 所示:
圖 4. 將 LDIF 文件導入到 ApacheDS
將 LDIF 文件導入到 ApacheDS 之后,JXplorer 將顯示用戶節點和部門節點樹,如?圖 1?所示。現在您可以開始配置 Acegi,使其能夠與您的 LDAP 服務器通信。
回頁首
為 LDAP 實現配置 Acegi
回想一下第 1 部分,其中 Acegi 使用身份驗證處理過濾器(Authentication Processing Filter,APF)進行身份驗證。APF 執行所有后端身份驗證處理任務,例如從客戶機請求中提取用戶名和密碼,從后端用戶庫讀取用戶參數,以及使用這些信息對用戶進行身份驗證。
您在第 1 部分中為屬性文件實現配置了 APF,現在您已將用戶庫存儲在 LDAP 目錄中,因此必須使用不同的方式配置過濾器來和 LDAP 目錄進行通信。首先看一下清單 2,它展示了在第 1 部分中的 “Authentication Processing Filter” 一節中如何為屬性文件實現配置 APF 過濾器:
清單 2. 為屬性文件配置 APF
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter"><property name="authenticationManager" ref="authenticationManager" /><property name="authenticationFailureUrl" value="/login.jsp?login_error=1" /><property name="defaultTargetUrl" value="/index.jsp" /><property name="filterProcessesUrl" value="/j_acegi_security_check" /></bean>查看一下清單 2,您曾經為 APF 提供了 4 個參數。您只需在 LDAP 服務器中為存儲重新配置第一個參數(authenticationManager)即可。其他三個參數保持不變。
配置身份驗證管理器
清單 3 展示了如何配置 Acegi 的身份驗證管理器,以實現與 LDAP 服務器的通信:
清單 3. 為 LDAP 配置 Acegi 的身份驗證管理器
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"><property name="providers"> <list> <ref local="ldapAuthenticationProvider" /> </list> </property></bean>在清單 3 中,org.acegisecurity.providers.ProviderManager?是一個管理器類,它管理 Acegi 的身份驗證過程。為此,身份驗證管理器需要一個或多個身份驗證提供者。您可以使用管理器 bean 的提供者屬性來配置一個或多個提供者。清單 3 只包含了一個提供者,即 LDAP 身份驗證提供者。
LDAP 身份驗證提供者處理所有與后端 LDAP 目錄的通信。您必須對其進行配置,下一節內容將討論該主題。
配置 LDAP 身份驗證提供者
清單 4 展示了 LDAP 身份驗證提供者的配置:
清單 4. 配置 LDAP 身份驗證提供者
<bean id="ldapAuthenticationProvider" class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider"><constructor-arg><ref local="authenticator"/></constructor-arg><constructor-arg><ref local="populator"/></constructor-arg></bean>注意 LDAP 身份驗證提供者類的名稱為?org.acegisecurity.providers.ldap.LdapAuthenticationProvider?。其構造函數包含兩個參數,使用兩個?<constructor-arg>?標記的形式,如清單 4 所示。
LdapAuthenticationProvider?構造函數的第一個參數是?authenticator,該參數通過檢查用戶的用戶名和密碼對 LDAP 目錄的用戶進行身份驗證。完成身份驗證后,第二個參數?populator?將從 LDAP 目錄中檢索有關該用戶的訪問權限(或業務角色)信息。
以下小節將向您展示如何配置驗證器和填充器 bean。
回頁首
配置驗證器
authenticator?bean 將檢查具有給定用戶名和密碼的用戶是否存在于 LDAP 目錄中。Acegi 提供了名為org.acegisecurity.providers.ldap.authenticator.BindAuthenticator?的驗證器類,它將執行驗證用戶名和密碼所需的功能。
配置?authenticator?bean,如清單 5 所示:
清單 5. 配置驗證器 bean
<bean id="authenticator" class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator"><constructor-arg><ref local="initialDirContextFactory"/></constructor-arg><property name="userDnPatterns"> <list> <value>uid={0},ou=employees,ou=partners</value> <value>uid={0},ou=customers,ou=partners</value> <value>uid={0},ou=suppliers,ou=partners</value> </list> </property><property name="userSearch"><ref local="userSearch"/></property></bean>在清單 5 中,BindAuthenticator?構造函數具有一個參數,使用?<constructor-arg>?標記的形式。清單 5 中參數的名稱為initialDirContextFactory。該參數實際上是另一個 bean,稍后您將學習如何配置該 bean。
目前為止,只知道?initialDirContextFactory?bean 的作用就是為稍后的搜索操作指定初始上下文。初始上下文是一個 DN,它指定了 LDAP 目錄內某個節點。指定初始上下文后,將在該節點的子節點中執行所有的搜索操作(例如查找特定用戶)。
例如,回到?圖 2?中查看?partners?節點,它的 DN 是?ou=partners,o=manufacturingEnterprise,dc=org。如果將?partners?節點指定為初始上下文,Acegi 將只在?partners?節點的子節點中查找用戶。
指定 DN 模式
除配置?BindAuthenticator?構造函數外,還必須配置?authenticator?bean 的兩個屬性(清單 5?中的兩個?<property>?標記)。
第一個?<property>?標記定義了一個?userDnPatterns?屬性,它封裝了一個或多個 DN 模式列表。DN 模式?指定了一組具有類似特性的 LDAP 節點(例如?圖 2?所示的?employees?節點的所有子節點)。
Acegi 的身份驗證器從?authenticator?bean 的?userDnPatterns?屬性中配置的每個 DN 模式構造了一個 DN。例如,查看?清單 5?中配置的第一個模式,即?uid={0},ou=employees,ou=partners。在進行身份驗證的時候,authenticator?bean 使用用戶提供的用戶名(比如?alice)替換了?{0}。使用用戶名取代了?{0}?之后,DN 模式將變為相對 DN(RDN)uid=alice,ou=employees,ou=partners,它需要一個初始上下文才能成為 DN。
例如,查看?圖 2?中的?alice's?條目。該條目是?employees?節點的第一個子節點。它的 DN 是uid=alice,ou=employees,ou=partners,o=manufacturingEnterprise, dc=org。如果使用?o=manufacturingEnterprise,dc=org?作為初始上下文并將其添加到 RDN?uid=alice,ou=employees,ou=partners?之后,將獲得 alice 的 DN。
使用這種方法通過 DN 模式構建了用戶的 DN 后,authenticator?將把 DN 和用戶密碼發送到 LDAP 目錄。目錄將檢查該 DN 是否具有正確的密碼。如果有的話,用戶就可以通過身份驗證。這個過程在 LDAP 術語中被稱為?bind 身份驗證。LDAP 還提供了其他類型的身份驗證機制,但是本文的示例只使用了 bind 身份驗證。
如果目錄中并沒有第一個 DN 模式創建的 DN,authenticator?bean 嘗試使用列表中配置的第二個 DN 模式。依此類推,authenticator?bean 將嘗試所有的 DN 模式來為進行身份驗證的用戶構造正確的用戶 DN。
搜索過濾器
回想一下較早的章節 “LDAP 目錄設置”,我在將用戶信息存儲到 LDAP 目錄時添加了一點靈活性。方法是在?圖 1?所示的?departments?節點內創建一個特定用戶(specialUser)。
如果試圖使用?清單 5?中配置的任何一種 DN 模式創建特定用戶的 DN,您會發現沒有一種 DN 模式可用。因此,當用戶嘗試登錄時,Acegi 的authenticator?bean 將不能夠構造正確的 DN,從而無法對該用戶進行身份驗證。
通過允許您指定搜索過濾器,Acegi 能夠處理類似的特殊情況。身份驗證器 bean 使用搜索過濾器查找不能夠通過 DN 模式構造 DN 進行身份驗證的用戶。
清單 5?中的第二個?<property>?標記具有一個?<ref>?子標記,它引用名為?userSearch?的 bean。userSearch?bean 指定搜索查詢。清單 6 展示了如何配置?userSearch?bean 來處理特定用戶:
清單 6. 配置搜索查詢以搜索特定用戶
<bean id="userSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch"><constructor-arg> <value>ou=departments</value> </constructor-arg><constructor-arg> <value>(uid={0})</value> </constructor-arg><constructor-arg> <ref local="initialDirContextFactory" /> </constructor-arg><property name="searchSubtree"> <value>true</value> </property></bean>搜索查詢的參數
清單 6 展示了?userSearch?bean 是?org.acegisecurity.ldap.search.FilterBasedLdapUserSearch?類的一個實例,該類的構造函數具有三個參數。第一個參數指定?authenticator?在哪個節點中搜索用戶。第一個參數的值為?ou=departments,該值是一個 RDN,指定了?圖 2?所示的?departments?節點。
第二個參數?(uid={0})?指定了一個搜索過濾器。由于使用?uid?屬性指定用戶,因此可以通過查找?uid?屬性具有特定值的節點來查找用戶。正如您所料,花括號里面的 0 向 Acegi 表示使用進行身份驗證的用戶的用戶名(本例中為?specialUser)替換?{0}。
第三個參數是對討論?清單 5?中的?BindAuthenticator?構造函數時引入的相同初始上下文的引用。回想一下,當指定了初始上下文后,稍后將在該初始上下文節點的子節點內進行所有的搜索操作。注意,應將指定為?清單 5?中第一個參數(ou=departments)的值的 RDN 前加到初始上下文。
除了這三個構造器參數,清單 6 所示的?userSearch?bean 還具有一個名為?searchSubtree?的屬性。如果將其值指定為?true,搜索操作將包括節點的子樹(即所有子節點、孫節點、孫節點的子節點等),該節點被指定為構造函數的第一個參數的值。
authenticator?bean 的配置完成后,下一步將查看?populator?bean 的配置,如?清單 4?所示。
回頁首
配置 populator
populator?bean 將讀取已經通過?authenticator?bean 身份驗證的用戶的業務角色。清單 7 展示?populator?bean 的 XML 配置:
清單 7. populator bean 的 XML 配置
<bean id="populator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"><constructor-arg> <ref local="initialDirContextFactory"/> </constructor-arg><constructor-arg> <value>ou=departments</value> </constructor-arg><property name="groupRoleAttribute"> <value>ou</value> </property><property name="searchSubtree"> <value>true</value> </property></bean>在清單 7 中,populator?bean 的構造函數包括 2 個參數,以及一個?groupRoleAttribute?屬性。構造函數的第一個參數指定了?populatorbean 用來讀取經過驗證用戶的業務角色的初始上下文。并不強制要求?authenticator?和?populator?bean 使用相同的初始上下文。您可以為這兩者分別配置一個初始上下文。
第二個構造函數參數指定了 populator 前加到初始上下文的 RDN。因此,RDN 組成了包含組用戶的節點的 DN,例如?departments?節點。
populator?bean 的?groupRoleAttribute?屬性指定了持有組成員業務角色數據的屬性。回想?設置 LDAP 目錄?一節中,您將每組用戶的業務角色信息存儲在名為?ou?的屬性中。然后將?ou?設置為?groupRoleAttribute?屬性的值,如?清單 7?所示。
如您所料,populator?bean 將搜索整個 LDAP 目錄來查找經過驗證的用戶所屬的組節點。然后讀取組節點的?ou?屬性的值,獲取用戶經過授權的業務角色。
這樣就完成了?populator?bean 的配置。目前為止,我們在三個位置使用了初始上下文:清單 5、清單 6?和?清單 7。接下來將了解如何配置初始上下文。
配置初始上下文
清單 8 展示了在 Acegi 中配置初始上下文的過程:
清單 8. 初始上下文的 XML 配置
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"><constructor-arg value="ldap://localhost:389/o=manufacturingEnterprise,dc=org"/><property name="managerDn"> <value>cn=manager,o=manufacturingEnterprise,dc=org</value> </property><property name="managerPassword"> <value>secret</value> </property></bean>清單 8 中 Acegi 的初始上下文類的名稱為?org.acegisecurity.ldap.DefaultInitialDirContextFactory,這是 Acegi 包含的工廠類。Acegi 在內部使用該類構造其他處理目錄操作(例如在整個目錄中搜索)的類的對象。當配置初始上下文工廠時,必須指定以下內容:
- 將您的 LDAP 目錄和根目錄節點的網絡地址指定為構造函數的參數。在初始上下文配置的節點將作為根節點。就是說所有后續操作(例如search)都將在根節點定義的子樹中執行。
- 將 DN 和密碼分別定義為?managerDn?和?managerPassword?屬性。在執行任何搜索操作之前,Acegi 必須使用目錄服務器對 DN 和密碼進行身份驗證。
您已經了解了如何將用戶庫托管在 LDAP 目錄中,以及如何配置 Acegi 來使用來自 LDAP 目錄的信息對用戶進行身份驗證。下一節將進一步介紹 Acegi 的身份驗證處理過濾器,了解新配置的 bean 是如何管理身份驗證過程的。
回頁首
身份驗證和授權
APF 配置完成后,將能夠與 LDAP 目錄進行通信來對用戶進行身份驗證。如果您閱讀過第 1 部分,那么對與目錄通信過程中 APF 執行的一些步驟不會感到陌生,我在第 1 部分中向您展示了過濾器如何使用不同的服務進行用戶身份驗證。圖 5 所示的序列表與您在?第 1 部分圖 3?看到的非常類似:
圖 5. APF 對一名 LDAP 用戶進行身份驗證
無論 APF 使用屬性文件進行內部的身份驗證還是與 LDAP 服務器進行通信,步驟 1 到步驟 9 與第 1 部分是相同的。這里簡單描述了前 9 個步驟,您可以從步驟 10 開始繼續學習特定于 LDAP 的事件:
不論使用何種身份驗證方法,最后三個步驟是相同的(步驟21、21 和 23)。
配置攔截器
您已經了解了 APF 對用戶進行身份驗證的步驟。接下來是查看成功進行身份驗證的用戶是否被授權訪問所請求的資源。這項任務由 Acegi 的攔截過濾器(Interceptor Filter,IF)完成。本節將向您展示如何配置 IF 來實現訪問控制策略。
回想一下在?第 1 部分的清單 7?中配置 IF 的步驟。攔截過濾器在資源和角色之間建立映射,就是說只有具備必要角色的用戶才能訪問給定資源。為了演示制造業企業中不同部門的業務角色,清單 9 向現有的 IF 配置添加了另外的角色:
清單 9. 配置攔截過濾器
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"><property name="authenticationManager" ref="authenticationManager" /><property name="accessDecisionManager" ref="accessDecisionManager" /><property name="objectDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /protected/engineering/**=ROLE_HEAD_OF_ENGINEERING /protected/marketing/**=ROLE_HEAD_OF_MARKETING /**=IS_AUTHENTICATED_ANONYMOUSLY </value> </property></bean>在清單 9 中,IF 包含三個參數。其中第一個和第三個參數與第 1 部分中最初配置的參數相同。這里添加了第二個參數(名為accessDecisionManager?的 bean)。
accessDecisionManager?bean 負責指定授權決策。它使用清單 9 中第三個參數提供的訪問控制定義來指定授權(或訪問控制)決策。第三個參數是?objectDefinitionSource。
配置訪問決策管理器
accessDecisionManager?決定是否允許一個用戶訪問某個資源。Acegi 提供了一些訪問決策管理器,它們指定訪問控制決策的方式有所不同。本文只解釋了其中一種訪問決策管理器的工作方式,其配置如清單 10 所示:
清單 10. 配置訪問決策管理器
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"><property name="decisionVoters"> <list> <bean class="org.acegisecurity.vote.RoleVoter"/> <bean class="org.acegisecurity.vote.AuthenticatedVoter" /> </list> </property></bean>在清單 10 中,accessDecisionManager?bean 是?org.acegisecurity.vote.AffirmativeBased?類的實例。accessDecisionManager?bean 只包含一個參數,即投票者(voter)列表。
在 Acegi 中,投票者確定是否允許某個用戶訪問特定的資源。當使用?accessDecisionManager?查詢時,投票者具有三個選項:允許訪問(access-granted)、拒絕訪問(access-denied),如果不確定的話則放棄投票(abstain from voting)。
不同類型的訪問決策管理器解釋投票者決策的方法也有所不同。清單 10 所示的?AffirmativeBased?訪問決策管理器實現了簡單的決策邏輯:如果任何投票者強制執行肯定投票,將允許用戶訪問所請求的資源。
投票者邏輯
Acegi 提供了若干個投票者實現類型。accessDecisionManager?將經過驗證的用戶的信息(包括用戶的業務角色信息)和objectDefinitionSource?對象傳遞給投票者。本文的示例使用了兩種類型的投票者,RoleVoter?和?AuthenticatedVoter,如清單 10 所示。現在看一下每種投票者的邏輯:
- RoleVoter?只有在?objectDefinitionSource?對象的行中找到以?ROLE_?前綴開頭的角色時才進行投票。如果?RoleVoter?沒有找到這樣的行,將放棄投票;如果在用戶業務角色中找到一個匹配的角色,它將投票給允許訪問;如果沒有找到匹配的角色,則投票給拒絕訪問。在?清單 9?中,有兩個角色具有?ROLE_?前綴:ROLE_HEAD_OF_ENGINEERING?和?ROLE_HEAD_OF_MARKETING。
- AuthenticatedVoter?只有在?objectDefinitionSource?對象中找到具有某個預定義角色的行時才進行投票。在?清單 9?中,有這樣一行:IS_AUTHENTICATED_ANONYMOUSLY。匿名身份驗證意味著用戶不能夠進行身份驗證。找到該行后,AuthenticatedVoter?將檢查一個匿名身份驗證的用戶是否可以訪問某些不受保護的資源(即這些資源沒有包含在具備?ROLE_?前綴的行中)。如果?AuthenticatedVoter?發現所請求的資源是不受保護的并且?objectDefinitionSource?對象允許匿名身份驗證的用戶訪問不受保護的資源,它將投票給允許訪問;否則就投票給拒絕訪問。
回頁首
示例應用程序
本文提供了一個示例應用程序,它將演示您目前掌握的 LDAP 和 Acegi 概念。LDAP-Acegi 應用程序將顯示一個索引頁面,該頁面將設計和銷售文檔呈現給合適的經過身份驗證的用戶。正如您將看到的一樣,LDAP-Acegi 應用程序允許用戶?alice?查看設計文檔,并允許用戶?bob?查看銷售文檔。它還允許特定用戶同時查看設計和銷售文檔。所有這些內容都是在本文開頭配置 LDAP 目錄服務器時設置的。立即?下載示例應用程序來開始使用它。
回頁首
結束語
在本文中,您了解了如何將用戶和業務角色信息托管在 LDAP 目錄中。您還詳細了解了配置 Acegi 的方法,從而與 LDAP 目錄交互實現訪問控制策略。在本系列最后一期文章中,我將展示如何配置 Acegi 來保護對 Java 類的訪問。
第 3 部分: 實現對 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 安全性以外,這些場景還要求您對單獨的類訪問進行保護。
業務自動化
業務自動化應用程序中的工作流由多個流程組成。例如,病理學實驗室中執行血液測試的工作流由若干個步驟組成,其中每個步驟可看作一個業務流程:
很明顯,每個流程分別由單獨的授權用戶執行。未授權的用戶則無權執行流程。例如,實驗室研究人員只負責準備試驗結果,而無權編寫測試報告。
幾乎所有的業務自動化應用程序都普遍使用授權的業務流程。通常,每個業務流程被實現為一個 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 對象
現在我們將詳細討論這些步驟:
您現在已了解到如何從 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. BeanNameAutoProxyCreator。BeanNameAutoProxyCreator?類是 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 代理。
現在考慮代理創建器如何在內部創建代理對象:
Cglib2AopProxy?類實現了兩個名為?AOPProxy?和?MethodInterceptor?的接口。AOPProxy?接口由 Spring 框架提供,表示您希望進行代理的實際 bean,因此它與您的 bean 公開相同的方法。MethodInterceptor?接口也源于 AOP 框架,它包含的方法可以在用戶試圖訪問您已執行代理的 bean 時接受控制權。這意味著?MethodInterceptor?接口處理來自用戶的請求以訪問執行過代理的 bean。由于?Cglib2AopProxy?類同時實現了?AOPProxy?和?MethodInterceptor?接口,因此它提供了完整的功能,既可以提供經過代理的 bean,也可以處理用戶請求以訪問代理 bean(參見?參考資料小節?中有關 AOP 的討論文章的鏈接)。
執行完前面的步驟后,您現在具有了所需的代理對象。因此?XMLWebApplicationContext?類將安全 bean 的代理(而不是實際的 bean)保存在 “使用 Spring 創建 Java 對象” 的步驟 7 中的同一個數組中。
回頁首
訪問執行過代理的 Java 對象
在前面的幾節中,您了解了 Spring 如何創建公有 bean 和私有 bean。出于本文的目的,您可將公有 bean 視為使用代理保護的不安全的私有 bean。現在我們來看一下客戶機應用程序為訪問公有 bean 和私有 bean 而必須遵循的一系列步驟。
清單 3 展示了?publicCatalog?和?privateCatalog?兩個 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 中配置的?publicCatalog?和?privateCatalog?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 的訪問:authenticationManager、accessDecisionManager和?objectDefinitionSource。
回憶一下,您在本系列第 1 部分的?配置身份驗證處理過濾器?中曾對?authenticationManager?屬性進行了配置。authenticationManager?屬性的作用是對用戶進行身份驗證。
您在本系列的第二篇文章中了解了?accessDecisionManager?屬性。這個訪問決策管理器負責制定授權決策。在允許對一個安全 bean 進行訪問之前,方法安全攔截器使用?authenticationManager?和?accessDecisionManager?屬性對用戶進行身份驗證和授權。
現在查看?清單 5?中配置的?objectDefinitionSource?屬性。它類似于第 1 部分中出現的?objectDefinitionSource?屬性。以前的objectDefinitionSource?包含類似于?/protected/*?和?/**?這樣的 URL,清單 5 中的?objectDefinitionSource?屬性指定類和方法名;例如,sample.PrivateCatalog?是之前執行過代理的類的名稱,而?getData?是您希望對其控制用戶訪問的方法的名字。
當用戶訪問?PrivateCatalog?bean 的?getData()?方法時,控制權將自動傳遞給攔截器。攔截器使用 Acegi 框架檢查用戶的業務角色是否為ROLE_HEAD_OF_ENGINEERING(特定于本文的示例)。如果是的話,攔截器將允許對?getData()?方法進行訪問。如果攔截器發現用戶角色不是ROLE_HEAD_OF_ENGINEERING,則拒絕訪問。
下一節將查看一個示例 Acegi 應用程序,它將實現您目前所了解的所有概念。
回頁首
示例 Acegi 應用程序
本文的?下載源代碼?包含了一個名為 AcegiMethodSecurity 的示例應用程序,可按照以下方法進行配置和部署:
啟動 Tomcat 并嘗試運行示例應用程序。
運行示例應用程序
通過從瀏覽器訪問 http://localhost:8080/acegiMethodSecurity URL 可調用示例應用程序。AcegiMethodSecurity 顯示的索引頁面包含兩個鏈接(Catalog?和?Login),如圖 2 所示:
圖 2. 示例應用程序的主頁面
當單擊應用程序的?Catalog?鏈接時,它將要求您進行登錄。如果以?alice?或?specialUser?的身份進行登錄,示例應用程序將提供完整的?類別,包括公有數據和私有數據。這是因為在?清單 5?中,您對方法安全性攔截器進行了配置,允許用戶使用?ROLE_HEAD_OF_ENGINEERING?訪問私有類別,而?alice?和?specialUser?都具有該訪問權。另一方面,如果您以?bob?的身份登錄,示例應用程序將僅顯示公有數據。
回頁首
為通過身份驗證的用戶分配額外角色
本節將演示經過增強的示例應用程序。增強后的示例應用程序將展示 Acegi 如何使您能夠在運行時向通過身份驗證的用戶臨時分配額外角色。
當安全 bean(例如?清單 3?的?privateCatalog?bean)要訪問一個原創資源時,您可能需要使用額外的角色。例如,您可能考慮到您的安全 bean 需要通過 Java 的 Remote Method Invocation (RMI) 框架或一個 Web 服務訪問某個遠程應用程序。訪問安全 bean 的用戶不會占用遠程應用程序要求訪問用戶所具備的業務角色。
在本例中,Acegi 首先檢查用戶是否經過授權來訪問安全 bean。之后,Acegi 允許用戶訪問安全 bean。當安全 bean 試圖訪問遠程服務時,它需要使用額外的業務角色。如果訪問安全 bean 的用戶不具備額外角色,安全 bean 就不能成功訪問遠程服務。
run-as-replacement 機制
Acegi 框架提供了一種名為?run-as-replacement?的簡單機制,允許您僅在方法調用期間為通過身份驗證的用戶配置一個或多個額外角色。您可以使用 run-as-replacement 機制為訪問遠程應用程序的安全 bean 配置額外角色。這意味著只要安全 bean 需要訪問遠程應用程序,Acegi 將為用戶裝載額外角色,從而允許安全 bean 訪問遠程應用程序。
清單 6 對?清單 5?中的方法安全性攔截器的配置進行了增強。增強后的配置使用了 run-as-replacement 機制。
清單 6. Acegi 方法安全性攔截器的增強配置
點擊查看代碼清單
清單 6 使用粗體顯示了兩處增強(與?清單 5?相比)。第一處增強為?runAsManager?屬性。runAsManager?屬性的作用是向通過身份驗證的用戶動態添加角色。出于這個目的,runAsManager?屬性包含了?RunAsManagerImpl?bean 的定義。RunAsManagerImpl?bean 只有在滿足下面的條件時才可變為活躍狀態:在?objectDefinitionSource?方法的角色定義中找到以?RUN_AS_?為前綴的角色。例如,PrivateCatalog.getData()?方法的角色定義(清單 6?中以粗體顯示的第二處增強)具有一個?RUN_AS_MANAGER?角色。
RunAsManagerImpl?bean 包含一個名為?key?的屬性,它封裝的加密鍵用于確保只將額外的角色作為 run-as-replacement 程序的一部分生成。
當用戶調用?getData()?方法時,RunAsManagerImpl?bean 變為活躍狀態并創建名為?RUN_AS_MANAGER?的額外角色,從而啟用?getData()?方法訪問遠程應用程序。
增強的方法安全性
本文的?下載源代碼?包含一個名為?EnhancedAcegiMethodSecurity?的示例應用程序,它可以演示 run-as-replacement 機制和程序。該應用程序將顯示一個具有 Catalog 鏈接的索引頁面。如果單擊?Catalog?鏈接,將要求進行登錄。
登錄后,EnhancedAcegiMethodSecurity?應用程序將為您提供登錄用戶及其角色的完整信息。例如,如果以?alice?或?specialUser?身份登錄,將向您顯示用戶的所有業務角色,包括額外創建的臨時的?RUN_AS_MANAGER?角色。
結束語
在這份共分三部分的系列文章中,我介紹了如何使用 Acegi 安全系統增強基于 URL 的安全性和基于方法的安全性。您了解了如何設計訪問控制策略并將它們托管在目錄服務器中,如何對 Acegi 進行配置以與目錄服務器進行通信,以及如何根據托管在服務器的訪問控制策略制定身份驗證和授權決策。
本系列的最后一篇文章主要介紹使用基于方法的安全性保護 Java 類實例。文章還解釋了 Acegi 和 Spring 如何在內部創建和代理 Java 對象以及 bean 代理如何實現訪問控制。文章包含了兩個示例應用程序,您可以使用它們進一步研究本系列中學到的概念,更多有關使用 Acegi 保護 Java 應用程序的內容,請參見?參考資料?小節。
?
原文來自:
http://www.ibm.com/developerworks/cn/java/j-acegi1/index.html
http://www.ibm.com/developerworks/cn/java/j-acegi2/
http://www.ibm.com/developerworks/cn/java/j-acegi3/
?
轉載于:https://www.cnblogs.com/davidwang456/p/3818846.html
總結
以上是生活随笔為你收集整理的使用 Acegi 保护 Java 应用程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: j2ee安全介绍--转
- 下一篇: 深入分析 Java I/O 的工作机制-