知識積累(九)——Acegi (version1.0.4)中文參考手冊——第六章 通用認證服務

 

第六章. 通用認證服務

6.1. Mechanisms, Providers Entry Points

如果你使用Acegi Security提供的認證方法,那麼通常你需要配置一個web filter,一個AuthenticationProvider

以及AuthenticationEntryPoint。在本節我們將要瀏覽一個示例應用,它需要支持基於form的認證(例如提供給用戶登錄的HTML頁面)以及基礎認證(例如web service或者類似的可以訪問受保護資源)。

 

web.xml中,這個應用需要一個單獨的Acegi Security filter來使用FilterChainProxy。幾乎所有的Acegi Security應用都有一個類似的項,看起來象下面這樣:

 

xml 代碼

  1. <filter>  

  2. <filter-name>Acegi Filter Chain Proxy</filter-name>  

  3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>  

  4. <init-param>  

  5. <param-name>targetClass</param-name>  

  6. <param-value>org.acegisecurity.util.FilterChainProxy</param-value>  

  7. </init-param>  

  8. </filter>  

  9. <filter-mapping>  

  10. <filter-name>Acegi Filter Chain Proxy</filter-name>  

  11. <url-pattern>/*</url-pattern>  

  12. </filter-mapping>  

 

上述聲明將使每個web請求都要經過Acegi SecurityFilterChainProxy。正如在本手冊的filter那節中所說,FilterChainProxy是一個通用類,它使得web請求按照URL模式被髮送到不同的filter。那些被委派的filter是由application context管理的,因此它們可以享受依賴注射的好處。我們來看看在你的application contextFilterChainProxy的定義會是什麼樣的:

 

xml 代碼

  1. <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">  

  2. <property name="filterInvocationDefinitionSource">  

  3. <value>  

  4. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON   

  5. PATTERN_TYPE_APACHE_ANT   

  6. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,</value>  

  7. </property>  

  8. </bean>  

 

在內部,Acegi Security會使用PropertyEditor來將上述XML片段中的字符串轉化爲一個FilterInvocationDefinitionSource對象。在這個階段需要注意的是,一系列的filter會按照定義的順序運行,並且這些filter實際就是application context中的bean的。所以,在我們的例子中,會在application context出現另外一些bean,它們會被命名爲httpSessionContextIntegrationFilter, logoutFilter 等。Filter出現的順序會在手冊中filter那一節討論,雖然上述的例子中它們是正確的。

 

在我們的例子中,我們使用了AuthenticationProcessingFilterBasicProcessingFilter。它們分別對應了基於form的認證和BASIC HTTP header-based認證的認證機制(我們在手冊的前面部分討論了認證機制扮演的角色)。如果你既不使用form也不使用BASIC認證,就不需要定義這些bean了。取而代之的是你要定義對應你所需要的認證環境的filter,例如DigestProcessingFilter 或者CasProcessingFilter。請對照手冊中對應的章節來了解如何配置這些認證機制。

 

讓我們回憶一下,在HttpSessionContextIntegrationFilter中保存了每個HTTP session調用中的SecurityContext。這意味着認證機制只會在principal最初嘗試認證的時候被使用一次。在餘下的時間內,認證機制只是靜靜的待在那裏,將請求發往filter鏈中的下一個filter。這個基於實際的需求源於這樣的一個事實,很少有認證實現在每一個,每一次的調用的時候都會進行認證(BASIC認證是一個值得注意的例外),但是如果一個pricipal在最初的認證步驟之後帳號被取消了,或者被禁用了,或者被修改了(例如GrantedAuthority[]中增加或者減少)會怎麼樣呢?讓我們來看看現在這些情況是如何處理的。

 

前面已經介紹了安全對象的主要認證provider AbstractSecurityInterceptor。這個類需要能夠訪問一個AuthenticationManager。它同時有個可選配置可以設定一個認證對象每次安全對象調用的時候是否需要重新認證。如果Authentication.isAuthenticated()返回true,那麼它默認在SecurityContextHolder中的認證對象是已認證的。這樣做對於提高性能是非常好的,但是對於即時的認證驗證是不理想的。在這樣的情況下你可能需要將AbstractSecurityInterceptor.alwaysReauthenticate屬性設置爲true

 

你可能會問自己這個AuthenticationManager是什麼?我們之前沒有見過它,但是我們曾經討論過AuthenticationProvider的概念。非常簡單,AuthenticationManager負責在AuthenticationProvider鏈之間傳遞請求。它非常象我們之前討論過的filter鏈,雖然有一些不同。Acegi Security只提供了一個AuthenticationManager實現,因此讓我們看看對於我們這章的例子,它是如何配置的:

 

xml 代碼

  1. <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">  

  2. <property name="providers">  

  3. <list>  

  4. <ref local="daoAuthenticationProvider"/>  

  5. <ref local="anonymousAuthenticationProvider"/>  

  6. <ref local="rememberMeAuthenticationProvider"/>  

  7. </list>  

  8. </property>  

  9. </bean>  

 

在這個時候,可能值得提到的是你的認證機制(通常是filter)也被注入了一個AuthenticationManager的引用。所以和認證機制都會使用上述的ProviderManager來輪詢一系列的AuthenticationProvider

 

在我們例子中有三個provider。它們按照上述的順序調用(使用list而不是set來顯示是按照順序調用的),每個provider都能夠嘗試認證,或者僅僅返回一個null來跳過認證。如果所有的實現都返回nullProviderManager會拋出一個相應的異常。如果你想了解更多chaining providers的信息,請參閱ProviderManagerJavaDoc

 

authentication mechanism使用的那些provider有時候是可以互換的,而有時候它們又依賴於特定的authentication mechanism。例如,DaoAuthenticationProvider只需要一個基於字符串的用戶名和密碼。若干個認證機制會產生基於字符串的用戶名和密碼的集合,包括(但不限於)BASIC form 認證。同時,有些認證機制會產生一個只能和特定類型的AuthenticationProvider交互的認證請求對象。一個這種一對一映射的例子是JA-SIG CAS,它使用service ticket的概念,只能被Common Authentication Services CasAuthenticationProvider認證。一個更加深入的一對一映射的例子是LDAP認證機制,它只能由LdapAuthenticationProvider處理。這種特定的對應關係在每個類的JavaDoc以及在本手冊的特定認證方法章節中有詳細說明。你不用擔心這些實現的細節,因爲如果你忘記註冊一個合適的provider,你在嘗試認證時只會收到一個ProviderNotFoundException異常。

 

當你在FilterChainProxy中正確配置了認證機制,並且確保註冊了對應的AuthenticationProvider,你的最後一步是配置一個AuthenticationEntryPoint。回憶一下早先我們討論過的ExceptionTranslationFilter的角色,當一個基於HTTP的請求收到一個HTTP頭或者一個HTTP重定向以開始認證時它被使用。繼續我們早先的例子:

 

xml 代碼

  1. <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">  

  2. <property name="authenticationEntryPoint"><ref  

  3. local="authenticationProcessingFilterEntryPoint"/></property>  

  4. <property name="accessDeniedHandler">  

  5. <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">  

  6. <property name="errorPage" value="/accessDenied.jsp"/>  

  7. </bean>  

  8. </property>  

  9. </bean>  

  10. <bean id="authenticationProcessingFilterEntryPoint"  

  11. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">  

  12. <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>  

  13. <property name="forceHttps"><value>false</value></property>  

  14. </bean>  

 

注意到ExceptionTranslationFilter需要兩個協作者。第一個AccessDeniedHandlerImpl,使用一個RequestDispatcher導向顯示特定的訪問拒絕的錯誤頁面。我們使用forwad所以SecurityContextHolder中仍然保留principal的詳細信息,這些對於顯示給用戶來說是有用的(在Acegi Security的老版本中,我們依賴rervlet容器來處理403錯誤信息,它缺乏這個有用的上下文信息)。AccessDeniedHandlerImpl同時將會將HTTP頭設置爲403,它是訪問拒絕的正式錯誤代碼。至於AuthentionEntryPoint,這裏設置如果一個未受認證的principal嘗試執行一個受保護的操作時,我們需要執行那些動作。因爲在我們的例子中要使用基於form的認證,因此我們設定AuthenticationProcessinFilterEntryPoint以及登錄頁面的URL。你的應用系統通常只需要一個entry point,並且大多數的認證方法都定義了自己特有的AuthenticationEntryPoint。每個認證方式所對應的特定entry point的詳細情況會在本手冊特定的認證方法章節中介紹。

6.2. UserDetails Associated Types

正如在第一部分中提到的,大多數認證provider要用到UserDetails UserDetailsService 接口。後面那個接口只包含一個方法:

 

java 代碼

  1. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,   

  2. DataAccessException;  

返回值UserDetails是一個接口,它提供了若干個getter保證返回非null值,例如用戶名,密碼,授予的權限以及用戶是啓用還是禁用狀態。大部分認證provider都會使用一個,即使它在認證判斷過程中實際並不使用用戶名和密碼。通常這些provider只會使用返回的UserDetails中的GrantedAuthority[]信息,因爲有些系統(例如LDAP X509 CAS)已經承擔了實際的身份驗證的責任。

 

Acegi Security提供了一個UserDetails的實體類實現-UserAcegi Security用戶需要確定什麼時候實現UserDetailsService以及返回什麼樣的UserDetails實體類。通常,直接使用User類或者繼承User類就可以了,儘管有一些特殊情況(例如 object relational mappers),需要用戶從頭寫他們自己的UserDetails實現。這種情況也時有發生,用戶只要返回他們正常的代表系統用戶的領域對象就可以了。特別是UserDetails經常被用來存儲額外的principal相關屬性(例如他們的電話號碼以及email地址),這樣它們可以很容易被web視圖使用。

 

特定的UserDetailsService實現起來是很簡單的,它應該很容易由用戶來選擇持久化策略來獲取認證信息。說到這裏,Acegi Security確實包含了一些有用的基礎實現,下面讓我們看一下。

 

6.2.1. In-Memory 認證

雖然用戶可以創建一個定製的UserDetailsService實現來從一個持久化引擎中獲取信息,很多應用不需要這種複雜性。特別是如果你正在進行快速原型開發或者剛開始集成Acegi Security,當你不需要花費時間來進行數據庫配置或者寫UserDetailsService的實現。這種情況之下,你有一個簡單的選擇,就是配置InMemoryDaoImpl實現。

 

xml 代碼

  1. <bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">  

  2. <property name="userMap">  

  3. <value>  

  4. marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR   

  5. dianne=emu,ROLE_TELLER   

  6. scott=wombat,ROLE_TELLER   

  7. peter=opal,disabled,ROLE_TELLER   

  8. </value>  

  9. </property>  

  10. </bean>  

 

在上面的例子中,userMap屬性包含了每個用戶的用戶名,密碼,一個授權列表以及一個可選的啓用/禁用關鍵詞。使用逗號分隔。用戶名必須在等號的左側,密碼必須在等號右側第一個出現。啓用和禁用關鍵詞(大小寫敏感)可以出現在第二個或者之後任意位置。剩餘的字符串被看作是授予的權限,這些權錢被創建爲GrantedAuthorityImpl對象(僅供參考-大多數的應用不需要自定義的GrantedAuthority實現,所以使用默認的實現就可以了)。注意如果一個用戶沒有密碼及或沒有被授予權限,該用戶不會在in-memory 認證庫中創建。

 

InMemoryDaoImpl也提供了一個setUserProperties(Properties)方法,可以允許你用另一個Spring的配置好的bean或者一個外部的properties文件來實例化屬性。你可能要使用SpringPropertiesFactoryBean,它在加載外部屬性文件的時候非常有用。這個setter可能對於有大量用戶的應用,或者開發期配置變更有所助益,但是不要指望使用整個數據庫來處理認證細節。

 

6.2.2. JDBC 認證

也包括了一個從JDBC數據源獲取認證信息的UserDetailsService。使用Spring內部的JDBC,避免了僅僅爲了存儲用戶信息而使用複雜的對象關係Common Authentication Services 映射(ORM)。如果你確實使用ORM工具,你可能要寫一個定製的UserDetailsService來重用你已經創建的映射文件。回到JdbcDaoImpl,下面是一個配置的例子:

 

xml 代碼

  1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  

  2. <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>  

  3. <property name="url"><value>jdbc:hsqldb:hsql://localhost:9001</value></property>  

  4. <property name="username"><value>sa</value></property>  

  5. <property name="password"><value></value></property>  

  6. </bean>  

  7. <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  

  8. <property name="dataSource"><ref bean="dataSource"/></property>  

  9. </bean>  

  10.    

你可能要修改上述的DriverManagerDataSource來使用不同的關係數據庫管理系統。你還可以使用從JNDI獲取的全局數據源,如上的常規Spring選項。不論是使用什麼數據庫以及如何獲取數據源,必須使用一個按照dbinit.txt中寫明的數據庫模式。你可以從Acegi Security網站下載這個文件。

 

如果你的默認數據庫模式不能滿足需要,JdbcDaoImpl提供了兩個屬性允許定製SQL語句。如果需要進一步定製,你可以繼承JdbcDaoImpl。請參考JavaDocs獲取詳情,不過請注意這個類並不是爲了複雜的自定義繼承而寫的。如果你的需求比較複雜(例如數據庫結構比較特殊或者需要返回一個特定的UserDetails實現),那麼你最好寫自己的UserDetailsService實現。Acegi Security提供的基礎實現只是爲了典型場景,並沒有提供無限的配置靈活性。

 

6.3. 並行Concurrent Session 處理

Acegi Security能夠限定次數防止一個principal多次並行認證到同一個應用。許多ISV利用這一點來加強授權管理,網管也喜歡這個特性因爲可以防止一個用戶名被重複使用。例如,你可以限制“Batman”用戶從兩個不同的session登錄系統。

 

使用並行session支持,你需要在web.xml中增加如下內容:

 

xml 代碼

  1. <listener>  

  2. <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>  

  3. </listener>  

 

而且,你需要在中FilterChainProxy增加org.acegisecurity.concurrent.ConcurrentSessionFilter to your FilterChainProxyConcurrentSessionFilter需要兩個屬性,sessionRegistry用來指向一個SessionRegistryImpl實例,expiredUrl指向一個session實效時顯示的頁面。

 

當一個HttpSession開始或者結束的時候web.xml HttpSessionEventPublisher發送一個ApplicationEventSpring ApplicationContext。這很關鍵,因爲它確保session終止的時候SessionRegistryImpl會收到通知。

 

你還要裝配ConcurrentSessionControllerImpl並在ProviderManager中引用:

 

xml 代碼

1.                       <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">  

2.                       <property name="providers">  

3.                       <!-- your providers go here -->  

4.                       </property>  

5.                       <property name="sessionController"><ref bean="concurrentSessionController"/></property>  

6.                       </bean>  

7.                       <bean id="concurrentSessionController"  

8.                       class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">  

9.                       <property name="maximumSessions"><value>1</value></property>  

10.                 <property name="sessionRegistry"><ref local="sessionRegistry"/></property>  

11.                 </bean>  

12.                 <bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl"/>  

6.4. 認證標籤庫

AuthenticationTag只是用來把principalAuthentication.getPrincipal()對象的屬性顯示到web頁面。

 

下面的JSP片段展示瞭如何使用AuthenticationTag

java 代碼

  1. "username"/>  

 

這個標籤將會顯示pricipal的名字。這裏我們假設Authentication.getPrincipal()是一個UserDetails對象,這在使用典型的DaoAuthenticationProvider時候的一般狀況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章