Acegi 是一個基于 Spring 開發的安全框架,為應用程序提供基于 統一資源定位符 或 URL 和 方法訪問 的細粒度安全控制和保護功能。 Acegi Security System 使用安全過濾器來提供企業應用程序的身份驗證和授權服務。 Acegi 的設計仍是一個基于角色的權限控制系統,它通過一系列可配置的組件構建了一個基于 Spring IOC 組件裝配模式的安全框架。在 Acegi 安全框架中也有 Principal ( 通常是用戶名 ) 和 Credentials ( 通常是口令 ) 的概念,不過在 Acegi 框架中,通常將 UserDetails 作為 Principal ,除存儲用戶名外,還包含用戶角色等權限信息。 Authentication 對象還能存儲一些與認證請求有關的附加信息, e.g: 用戶 IP 地址。
Acegi 使用 SecurityContext 來存儲 Authentication ,默認情況下, Acegi 使用 ThreadLocal 來實現 SecurityContext 。在應用程序中通過 SecurityContextHolder.getContext().getAuthentication() 來獲取 Authentication 對象 。
?
四大組件
Acegi Security System 由四種主要類型的組件組成:過濾器、管理器、提供者和處理程序。
?
1. 過濾器
這種最高級的組件提供了常見的安全服務,如身份驗證、會話處理以及注銷。
2. 管理器
過濾器僅是安全相關功能的高級抽象:實際上要使用管理器和提供者實現身份驗證處理和注銷服務。管理器管理由不同提供者提供的較低級的安全服務。
3. 提供者
有大量的提供者可用于和不同類型的數據存儲服務通信,例如目錄服務、關系數據庫或簡單的內存中的對象。這意味著您可以將用戶庫和訪問控制協議存儲在任何一種這樣的數據存儲服務中,并且 Acegi 的管理器將在運行時選擇合適的提供者。
4. 處理程序
有時任務可能會被分解為多個步驟,其中每個步驟由一個特定的處理程序執行。比方說, Acegi 的 注銷過濾器 使用兩個處理程序來退出一個 HTTP 客戶機。其中一個處理程序使用戶的 HTTP 會話無效,而另一個處理程序則刪除用戶的 cookie 。當根據應用程序需求配置 Acegi 時,多個處理程序能夠提供很好的靈活性。您可以使用自己選擇的處理程序來執行保護應用程序所需的步驟。
?
安全過濾器
正如我前面提到的一樣, Acegi 使用安全過濾器為企業應用程序提供身份驗證和授權服務。您可以根據應用程序的需要使用和配置不同類型的過濾器。這一節將介紹五種最重要的 Acegi 安全過濾器。
?
Session Integration Filter ?
Acegi 的 Session Integration Filter ( SIF ) 通常是您將要配置的第一個過濾器。 SIF 創建了一個 安全上下文對象 ,這是一個與安全相關的信息的占位符。其他 Acegi 過濾器將安全信息保存在安全上下文中,也會使用安全上下文中可用的安全信息。
SIF 創建安全上下文并調用過濾器鏈中的其他過濾器。然后其他過濾器檢索安全上下文并對其進行更改。比如, Authentication Processing Filter 將用戶信息(如用戶名、密碼和電子郵件地址)存儲在安全上下文中。
當所有的處理程序完成處理后, SIF 檢查安全上下文是否更新。如果任何一個過濾器對安全上下文做出了更改, SIF 將把更改保存到服務器端的會話對象中。如果安全上下文中沒有發現任何更改,那么 SIF 將刪除它。
在 XML 配置文件中對 SIF 進行了配置,如清單 2 所示:
<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
所示:
<!---->
<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 所示:
<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 所示:
<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 成功執行身份驗證后對攔截過濾器進行配置,以使其發揮作用。攔截器使用應用程序的訪問控制策略來做出授權決定。
?
配置簡單的訪問控制策略可分為兩個步驟:
<!----> 1. 編寫訪問控制策略。 <!---->
<!----> 2. 根據策略配置 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. 托管 Acegi 過濾器鏈以安全地為瀏覽器客戶機服務的容器
?
?
下面的步驟描述了過濾器鏈的生命周期: ?
<!----> 1. 瀏覽器客戶機向您的應用程序發送 HTTP 請求。 <!---->
<!----> 2. 容器接收到 HTTP 請求并創建一個請求對象,該對象將封裝 HTTP 請求中包含的信息。容器還創建一個各種過濾器都可處理的響應對象,從而為發出請求的客戶機準備好 HTTP 響應。容器然后調用 Acegi 的過濾器鏈代理,這是一個代理過濾器。該代理知道應用的過濾器的實際順序。當容器調用代理時,它將向代理發送請求、響應以及過濾器鏈對象。 <!---->
<!----> 3. 代理過濾器調用過濾器鏈中第一個過濾器,向其發送請求、響應和過濾器鏈對象。 <!---->
<!----> 4. 鏈中的過濾器逐個執行其處理。一個過濾器可以通過調用過濾器鏈中下一個過濾器隨時終止自身處理。有的過濾器甚至根本不執行任何處理(比如,如果 APF 發現一個到來的請求沒有要求身份驗證,它可能會立即終止其處理)。 <!---->
<!----> 5. 當身份驗證過濾器完成其處理時,這些過濾器將把請求和響應對象發送到應用程序中配置的攔截過濾器。 <!---->
<!----> 6. 攔截器決定是否對發出請求的客戶機進行授權,使它訪問所請求的資源。 <!---->
<!----> 7. 攔截器將控制權傳輸給應用程序(比如,成功進行了身份驗證和授權的客戶機請求的 JSP 頁面)。 <!---->
<!----> 8. 應用程序改寫響應對象的內容。 <!---->
<!----> 9. 響應對象已經準備好了,容器將響應對象轉換為 HTTP 響應,并將響應發送到發出請求的客戶機。 <!---->
?
為幫助您進一步理解 Acegi 過濾器,我將詳細探討其中兩個過濾器的操作: Session Integration Filter 和 Authentication Processing Filter 。
?
SIF 如何創建一個安全上下文
現在詳細地考慮下面這些步驟:
?
<!----> 1. Acegi 的過濾器鏈代理調用 SIF 并向其發送請求、響應和過濾器鏈對象。注意:通常將 SIF 配置為過濾器鏈中第一個過濾器。 <!---->
<!----> 2. SIF 檢查它是否已經對這個 Web 請求進行過處理。如果是的話,它將不再進一步進行處理,并將控制權傳輸給過濾器鏈中的下一個過濾器(參見下面的第 4 個步驟)。如果 SIF 發現這是第一次對這個 Web 請求調用 SIF ,它將設置一個標記,將在下一次使用該標記,以表示曾經調用過 SIF 。 <!---->
<!----> 3. SIF 將檢查是否存在一個會話對象,以及它是否包含安全上下文。它從會話對象中檢索安全上下文,并將其放置在名為 security context holder 的臨時占位符中。如果不存在會話對象, SIF 將創建一個新的安全上下文,并將它放到 security context holder 中。注意: security context holder 位于應用程序的范圍內,所以可以被其他的安全過濾器訪問。 <!---->
<!----> 4. SIF 調用過濾器鏈中的下一個過濾器。 <!---->
<!----> 5. 其他過濾器可以編輯安全上下文。 <!---->
<!----> 6. SIF 在過濾器鏈完成處理后接收控制權。 <!---->
<!----> 7. SIF 檢查其他的過濾器是否在其處理過程中更改了安全上下文(比如, APF 可能將用戶詳細信息存儲在安全上下文中)。如果是的話,它將更新會話對象中的安全上下文。就是說在過濾器鏈處理過程中,對安全上下文的任何更改現在都保存在會話對象中。 <!---->
?
APF 如何對用戶進行身份驗證
現在仔細考慮以下這些步驟:
<!----> 1. 過濾器鏈中前面的過濾器向 APF 發送請求、響應和過濾鏈對象。 <!---->
<!----> 2. APF 使用從請求對象中獲得的用戶名、密碼以及其他信息創建身份驗證標記。 <!---->
<!----> 3. APF 將身份驗證標記傳遞給身份驗證管理器。 <!---->
<!----> 4. 身份驗證管理器可能包含一個或更多身份驗證提供者。每個提供者恰好支持一種類型的身份驗證。管理器檢查哪一種提供者支持它從 APF 收到的身份驗證標記。 <!---->
<!----> 5. 身份驗證管理器將身份驗證標記發送到適合進行身份驗證的提供者。 <!---->
<!----> 6. 身份驗證提供者支持從身份驗證標記中提取用戶名,并將它發送給名為 user cache service 的服務。 Acegi 緩存了已經進行過身份驗證的用戶。該用戶下次登錄時, Acegi 可以從緩存中加載他或她的詳細信息(比如用戶名、密碼和權限),而不是從后端數據存儲中讀取數據。這種方法使得性能得到了改善。 <!---->
<!----> 7. user cache service 檢查用戶的詳細信息是否存在于緩存中。 <!---->
<!----> 8. user cache service 將用戶的詳細信息返回給身份驗證提供者。如果緩存不包含用戶詳細信息,則返回 null 。 <!---->
<!----> 9. 身份驗證提供者檢查緩存服務返回的是用戶的詳細信息還是 null 。 <!---->
<!----> 10. 如果緩存返回 null ,身份驗證提供者將用戶名(在步驟 6 中提取)發送給另一個名為 user details service 的服務。 <!---->
<!----> 11. user details service 與包含用戶詳細信息的后端數據存儲通信(如目錄服務)。 <!---->
<!----> 12. user details service 返回用戶的詳細信息,或者,如果找不到用戶詳細信息則拋出身份驗證異常。 <!---->
<!----> 13. 如果 user cache service 或者 user details service 返回有效的用戶詳細信息,身份驗證提供者將使用 user cache service 或 user details service 返回的密碼來匹配用戶提供的安全標記(如密碼)。如果找到一個匹配,身份驗證提供者將用戶的詳細信息返回給身份驗證管理器。否則的話,則拋出一個身份驗證異常。 <!---->
<!----> 14. 身份驗證管理器將用戶的詳細信息返回給 APF 。這樣用戶就成功地進行了身份驗證。 <!---->
<!----> 15. APF 將用戶詳細信息保存在 圖 2 所示由步驟 3 創建的安全上下文中。 <!---->
<!----> 16. APF 將控制權傳輸給過濾器鏈中的下一個過濾器。 <!---->
?
一個簡單的 Acegi 應用程序
?
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> </property> </bean> <!-- SIF創建了一個安全上下文對象并調用過濾器鏈中的其他過濾器 --> <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" /> <!-- 管理注銷處理 --> <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> <!-- Acegi使用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="/protected/protected1.jsp" /> <!-- 指定了客戶在登錄窗口中按下 Sign In 按鈕時要訪問的 URL --> <property name="filterProcessesUrl" value="/j_acegi_security_check" /> </bean> <!-- 處理身份驗證和授權過程中的異常情況 --> <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> <!-- 在授權失敗的情況下,呈現的Access Denied 頁面 --> <property name="accessDeniedHandler"> <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"> <property name="errorPage" value="/accessDenied.jsp" /> </bean> </property> </bean> <!-- 用于做出授權決策的攔截過濾器 --> <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> </bean> <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" /> <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter"> <property name="key" value="changeThis" /> <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" /> </bean> <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider" /> </list> </property> </bean> <bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"> <property name="allowIfAllAbstainDecisions" value="false" /> <property name="decisionVoters"> <list> <bean class="org.acegisecurity.vote.RoleVoter" /> <bean class="org.acegisecurity.vote.AuthenticatedVoter" /> </list> </property> </bean> <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <!-- UserCache property will activate the cache, it is not mandatory but increases performance by cacheing the user details retrieved from user-base --> <property name="userCache" ref="userCache" /> </bean> <bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"> <property name="userProperties"> <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="location" value="/WEB-INF/users.properties" /> </bean> </property> </bean> <bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"> <property name="cache"> <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager"> <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" /> </property> <property name="cacheName" value="userCache" /> </bean> </property> </bean> <!-- This bean is optional; it isn't used by any other bean as it only listens and logs --> <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener" /> </beans>?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
