一 可以爲如下任務配置ACEGI安全過濾器:
- 在訪問一個安全資源之前提示用戶登錄。
- 通過檢查安全標記(如密碼),對用戶進行身份驗證。
- 檢查經過身份驗證的用戶是否具有訪問某個安全資源的特權。
- 將成功進行身份驗證和授權的用戶重定向到所請求的安全資源。
- 對不具備訪問安全資源特權的用戶顯示 Access Denied 頁面。
- 在服務器上記錄成功進行身份驗證的用戶,並在用戶的客戶機上設置安全 cookie。使用該 cookie 執行下一次身份驗證,而無需要求用戶登錄。
- 將身份驗證信息存儲在服務器端的會話對象中,從而安全地進行對資源的後續請求。
- 在服務器端對象中構建並保存安全信息的緩存,從而優化性能。
- 當用戶退出時,刪除爲用戶安全會話而保存的服務器端對象。
- 與大量後端數據存儲服務(如目錄服務或關係數據庫)進行通信,這些服務用於存儲用戶的安全信息和 ECM 的訪問控制策略。
acegi Security System 由四種主要類型的組件組成:過濾器、管理器、提供者和處理程序。
<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 的實例(名稱)。
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> |
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> |
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> |
Acegi 的攔截過濾器 用於做出授權決策。您需要在 APF 成功執行身份驗證後對攔截過濾器進行配置,以使其發揮作用。攔截器使用應用程序的訪問控制策略來做出授權決定。
本系列的下一篇文章將展示如何設計訪問控制策略,如何將它們託管在目錄服務中,以及如何配置 Acegi 以讀取您的訪問控制策略。但是,目前我將繼續向您展示如何使用 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_COMPARISON PATTERN_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 過濾器鏈以安全地爲瀏覽器客戶機服務的容器
下面的步驟描述了過濾器鏈的生命週期:
- 瀏覽器客戶機向您的應用程序發送 HTTP 請求。
- 容器接收到 HTTP 請求並創建一個請求對象,該對象將封裝 HTTP 請求中包含的信息。容器還創建一個各種過濾器都可處理的響應對象,從而爲發出請求的客戶機準備好 HTTP 響應。容器然後調用 Acegi 的過濾器鏈代理,這是一個代理過濾器。該代理知道應用的過濾器的實際順序。當容器調用代理時,它將向代理髮送請求、響應以及過濾器鏈對象。
- 代理過濾器調用過濾器鏈中第一個過濾器,向其發送請求、響應和過濾器鏈對象。
- 鏈中的過濾器逐個執行其處理。一個過濾器可以通過調用過濾器鏈中下一個過濾器隨時終止自身處理。有的過濾器甚至根本不執行任何處理(比如,如果 APF 發現一個到來的請求沒有要求身份驗證,它可能會立即終止其處理)。
- 當身份驗證過濾器完成其處理時,這些過濾器將把請求和響應對象發送到應用程序中配置的攔截過濾器。
- 攔截器決定是否對發出請求的客戶機進行授權,使它訪問所請求的資源。
- 攔截器將控制權傳輸給應用程序(比如,成功進行了身份驗證和授權的客戶機請求的 JSP 頁面)。
- 應用程序改寫響應對象的內容。
- 響應對象已經準備好了,容器將響應對象轉換爲 HTTP 響應,並將響應發送到發出請求的客戶機。
爲幫助您進一步理解 Acegi 過濾器,我將詳細探討其中兩個過濾器的操作:Session Integration Filter 和 Authentication Processing Filter。
圖 2 展示了 SIF 創建安全上下文所涉及到的步驟:
圖 2. SIF 創建安全上下文
現在詳細地考慮下面這些步驟:
- Acegi 的過濾器鏈代理調用 SIF 並向其發送請求、響應和過濾器鏈對象。注意:通常將 SIF 配置爲過濾器鏈中第一個過濾器。
- SIF 檢查它是否已經對這個 Web 請求進行過處理。如果是的話,它將不再進一步進行處理,並將控制權傳輸給過濾器鏈中的下一個過濾器(參見下面的第 4 個步驟)。如果 SIF 發現這是第一次對這個 Web 請求調用 SIF,它將設置一個標記,將在下一次使用該標記,以表示曾經調用過 SIF。
- SIF 將檢查是否存在一個會話對象,以及它是否包含安全上下文。它從會話對象中檢索安全上下文,並將其放置在名爲 security context holder 的臨時佔位符中。如果不存在會話對象,SIF 將創建一個新的安全上下文,並將它放到 security context holder 中。注意:security context holder 位於應用程序的範圍內,所以可以被其他的安全過濾器訪問。
- SIF 調用過濾器鏈中的下一個過濾器。
- 其他過濾器可以編輯安全上下文。
- SIF 在過濾器鏈完成處理後接收控制權。
- SIF 檢查其他的過濾器是否在其處理過程中更改了安全上下文(比如,APF 可能將用戶詳細信息存儲在安全上下文中)。如果是的話,它將更新會話對象中的安全上下文。就是說在過濾器鏈處理過程中,對安全上下文的任何更改現在都保存在會話對象中。
圖 3 展示了 APF 對用戶進行身份驗證所涉及到的步驟:
圖 3. APF 對用戶進行身份驗證
現在仔細考慮以下這些步驟:
- 過濾器鏈中前面的過濾器向 APF 發送請求、響應和過濾鏈對象。
- APF 使用從請求對象中獲得的用戶名、密碼以及其他信息創建身份驗證標記。
- APF 將身份驗證標記傳遞給身份驗證管理器。
- 身份驗證管理器可能包含一個或更多身份驗證提供者。每個提供者恰好支持一種類型的身份驗證。管理器檢查哪一種提供者支持它從 APF 收到的身份驗證標記。
- 身份驗證管理器將身份驗證標記發送到適合進行身份驗證的提供者。
- 身份驗證提供者支持從身份驗證標記中提取用戶名,並將它發送給名爲 user cache service 的服務。Acegi 緩存了已經進行過身份驗證的用戶。該用戶下次登錄時,Acegi 可以從緩存中加載他或她的詳細信息(比如用戶名、密碼和權限),而不是從後端數據存儲中讀取數據。這種方法使得性能得到了改善。
- user cache service 檢查用戶的詳細信息是否存在於緩存中。
- user cache service 將用戶的詳細信息返回給身份驗證提供者。如果緩存不包含用戶詳細信息,則返回 null。
- 身份驗證提供者檢查緩存服務返回的是用戶的詳細信息還是 null。
- 如果緩存返回 null,身份驗證提供者將用戶名(在步驟 6 中提取)發送給另一個名爲 user details service 的服務。
- user details service 與包含用戶詳細信息的後端數據存儲通信(如目錄服務)。
- user details service 返回用戶的詳細信息,或者,如果找不到用戶詳細信息則拋出身份驗證異常。
- 如果 user cache service 或者 user details service 返回有效的用戶詳細信息,身份驗證提供者將使用 user cache service 或 user details service 返回的密碼來匹配用戶提供的安全標記(如密碼)。如果找到一個匹配,身份驗證提供者將用戶的詳細信息返回給身份驗證管理器。否則的話,則拋出一個身份驗證異常。
- 身份驗證管理器將用戶的詳細信息返回給 APF。這樣用戶就成功地進行了身份驗證。
- APF 將用戶詳細信息保存在 圖 2 所示由步驟 3 創建的安全上下文中。
- APF 將控制權傳輸給過濾器鏈中的下一個過濾器。
在本文中,您已經瞭解了很多關於 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 框架。
部署並運行樣例應用程序非常的簡單。只需要完成兩件事情:
- 將 acegisample.war 文件從本教程下載的源代碼中複製到安裝 Tomcat 的
webapps
目錄中。
- 從 Acegi Security System 主頁 下載並解壓縮 acegi-security-1.0.3.zip。您將找到一個名爲 acegi-security-sample-tutorial.war 的樣例應用程序。解壓縮 war 文件並提取其 WEB-INF/lib 文件夾中所有的 jar 文件。將所有的 JAR 文件從 WEB-INF/lib 文件夾中複製到 theacegisample.war 應用程序的 WEB-INF/lib 文件夾。
現在,您已經爲運行樣例應用程序做好準備了。啓動 Tomcat 並將瀏覽器指向 http://localhost:8080/acegisample/
。
您將看到 圖 4 所示的歡迎頁面,但是此時顯示的頁面是真實的。請繼續運行程序,並查看在嘗試訪問歡迎頁面顯示的不同鏈接時會發生什麼狀況。