領先一步
VMware 提供培訓和認證,以加速您的進展。
瞭解更多隨著 Spring Security 2 中安全性結構描述的引入,建立一個簡單的安全應用程式並使其運作變得容易許多。在舊版本中,使用者必須個別宣告和連接所有實作 Bean,導致 Spring 應用程式 context 檔案龐大而複雜,難以理解和維護。學習曲線相當陡峭,我仍然記得當我開始從事這個專案(當時稱為 Acegi Security)時,在 2004 年,花了一些時間才理解所有內容。從正面來看,這種對框架基本構建模組的接觸意味著,一旦您設法組合出一個可運作的組態,幾乎不可能沒有至少了解一些重要的類別以及它們如何協同工作。這種知識反過來使您處於有利的位置,可以利用自訂的機會,而自訂是使用 Spring Security 的最大優勢之一。
現在我們有許多 Spring Security 使用者已經開始使用命名空間,並從它提供的簡便性和快速開發機會中受益,但是當您想要超越命名空間提供的功能時,事情就會變得更加困難。那時,您必須開始了解框架架構以及您的自訂類別將如何適應。您必須知道要擴展哪些類別、要實作哪些策略介面以及將它們插入到哪裡。學習曲線仍然存在,只是轉移了。命名空間有意提供 Spring Security 解決的問題領域的高階視圖,因此它實際上隱藏了實作細節,使得難以了解真正發生了什麼。它確實提供了許多擴充點,但無論出於何種原因,您可能會覺得需要更深入地挖掘。
在本文中,我們將看看一個簡單的 Web 應用程式的命名空間組態,以及它作為完整的 Spring Bean 組態會是什麼樣子。我們不會深入探討 Bean 的所有細節,但是您可以在參考手冊和 Javadoc 中找到有關特定類別和介面的更多資訊。此處的材料主要針對已經熟悉基礎知識的現有使用者,因此如果您以前從未使用過 Spring Security,您至少應該閱讀參考手冊中的命名空間章節,並花一些時間研究範例應用程式。
首先,讓我們看一下我們要替換的命名空間組態。
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http>
<intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />
<intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
<intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/images/*" filters="none" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.htm" default-target-url="/home.htm" />
<logout logout-success-url="/logged_out.htm" />
</http>
<authentication-manager>
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
<user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
這是一個非常簡單的範例,類似於您在線上範例和專案隨附的範例應用程式中看到的範例。它定義了一個記憶體內的使用者帳戶列表,用於驗證,其中包含每個使用者的授權列表(在本例中為簡單的角色)。它還組態了 Web 應用程式中的一組受保護 URL 模式、基於表單的驗證機制以及對基本登出 URL 的支援。
我們如何使用傳統的 Bean 組態來重現它?對於那些經歷過 Acegi Security 時代的人來說,是時候回顧一下歷史了。
首先,讓我們看一下 <authentication-manager> 元素,該元素(從 Spring Security 3.0 開始)必須在任何基於命名空間的組態中宣告。在本範例中,<http> 部分依賴於此元素(表單登入驗證機制使用它進行驗證)。實際的依賴項是介面AuthenticationManager,它封裝了 Spring Security 組態提供的驗證服務。您可以在此層級提供自己的實作,但是大多數人使用預設的ProviderManager,它委派給一組AuthenticationProvider實例。組態可能如下所示
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="authenticationProvider" />
<ref bean="anonymousProvider" />
</list>
</property>
</bean>
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="passwordEncoder">
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
</property>
<property name="userDetailsService" ref="userService" />
</bean>
<bean id="anonymousProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="SomeUniqueKeyForThisApplication" />
</bean>
<sec:user-service id="userService">
<sec:user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
<sec:user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
</sec:user-service>
</beans>
在此階段,我們保留了 <user-service> 元素,以說明它可以單獨使用來建立UserDetailsService實例,該實例被注入到DaoAuthenticationProvider中。我們也切換為使用 "beans" 作為預設的 XML 命名空間。從現在開始,我們將假設這一點。UserDetailsService是框架中的重要介面,只是一個使用者資訊的 DAO。它的唯一職責是載入具名使用者帳戶的資料。Bean 等效項將是
<bean id="userService" class="org.springframework.security.core.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
bob=12b141f35d58b8b3a46eea65e6ac179e,ROLE_SUPERVISOR,ROLE_USER
sam=d1a5e26d0558c455d386085fad77d427,ROLE_USER
</value>
</property>
</bean>
在這種情況下,命名空間語法更清晰,但是您可能想要使用自己的UserDetailsService實作。Spring Security 也有標準的基於 JDBC 和 LDAP 的版本。我們還加入了AnonymousAuthenticationProvider,它純粹是為了支援AnonymousAuthenticationFiter,它出現在下面的 Web 組態中。
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<property name="matcher">
<bean class="org.springframework.security.web.util.AntUrlPathMatcher"/>
</property>
<property name="filterChainMap">
<map>
<entry key="/somepath/**">
<list>
<ref local="filter1"/>
</list>
</entry>
<entry key="/images/*">
<list/>
</entry>
<entry key="/**">
<list>
<ref local="filter1"/>
<ref local="filter2"/>
<ref local="filter3"/>
</list>
</entry>
</map>
</property>
</bean>
其中 filter1、filter2 等是應用程式 context 中實作javax.servlet.Filter介面的其他 Bean 的名稱。
因此FilterChainProxy將傳入的請求匹配到篩選器列表,並將請求傳遞到它找到的第一個匹配鏈。請注意,除了 "/images/*" 模式(它映射到一個空的篩選器鏈)之外,這些與 <intercept-url< 命名空間元素中的模式沒有關聯。<http> 組態目前只能維護映射到所有請求的單個篩選器列表(除了那些組態為完全繞過篩選器鏈的請求)。
由於上面的組態有點冗長,因此有一種更緊湊的命名空間語法可用於組態FilterChainProxy映射,而不會丟失任何功能。上面的等效項將是
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/somepath/**" filters="filter1"/>
<sec:filter-chain pattern="/images/*" filters="none"/>
<sec:filter-chain pattern="/**" filters="filter1, filter2, filter3"/>
</sec:filter-chain-map>
</bean>
篩選器鏈現在被指定為 Bean 名稱的有序列表,順序是篩選器將被應用的順序。那麼我們的原始命名空間組態會建立哪些篩選器?在這種情況下,FilterChainProxy將是
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/images/*" filters="none"/>
<sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,
servletApiFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>
因此其中有九個篩選器,其中一些是可選的,而另一些是必要的。在這一點上,您可以看到您現在暴露於許多命名空間保護您免受影響的細節。您可以控制使用的篩選器以及它們被調用的順序,這兩者都至關重要。
我們還為 Bean 新增了別名,以匹配先前在web.xml中使用的名稱。或者,您可以直接使用 "filterChainProxy"。
我們現在將看看這九個篩選器 Bean 以及支援它們所需的其他 Bean。
<bean id="securityContextFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter" >
<property name="securityContextRepository" ref="securityContextRepository" />
</bean>
<bean id="securityContextRepository"
class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="/logged_out.htm" />
<constructor-arg>
<list><bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /></list>
</constructor-arg>
</bean>
<bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler">
<bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.jsp" />
</bean>
</property>
<property name="sessionAuthenticationStrategy">
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
</property>
</bean>
<bean id="requestCacheFilter" class="org.springframework.security.web.savedrequest.RequestCacheAwareFilter" />
<bean id="servletApiFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" />
<bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter" >
<property name="key" value="SomeUniqueKeyForThisApplication" />
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
</bean>
<bean id="sessionMgmtFilter" class="org.springframework.security.web.session.SessionManagementFilter" >
<constructor-arg ref="securityContextRepository" />
</bean>
<bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.htm"/>
</bean>
</property>
</bean>
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="securityMetadataSource">
<sec:filter-security-metadata-source>
<sec:intercept-url pattern="/secure/extreme/*" access="ROLE_SUPERVISOR"/>
<sec:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
<sec:intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/**" access="ROLE_USER" />
</sec:filter-security-metadata-source>
</property>
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
</bean>
再次,我們使用了一個方便的命名空間元素,filter-security-metadata-source,來建立SecurityMetadataSource實例,該實例由FilterSecurityInterceptor使用,但是您可以在此處插入自己的 Bean(有關範例,請參閱此常見問題解答)。filter-security-metadata-source元素建立了一個DefaultFilterInvocationSecurityMetadataSource.
此篩選器必須包含在任何篩選器鏈中。它負責在請求之間儲存驗證資訊(SecurityContext實例)。它還負責設定線程區域變數,在請求期間將驗證資訊儲存在其中,並在請求完成時清除它。它的預設策略是將SecurityContext儲存在 HTTP 會話中,因此使用了HttpSessionSecurityContextRepositoryBean。
LogoutFilter僅負責處理登出連結(/j_spring_security_logout預設情況下),清除安全性 context 並使會話失效。AnonymousAuthenticationFilter負責為匿名使用者填充安全性 context,使其更容易應用預設安全性限制,這些限制對於某些 URL 會放寬。例如,在上面的組態中,IS_AUTHENTICATED_ANONYMOUSLY屬性表示匿名使用者可以存取登入頁面(但不能存取其他任何內容)。查看手冊中關於此章節的更多資訊。它的使用是可選的,如果您不使用它,可以移除額外的AnonymousAuthenticationProvider。
SecurityContextHolderAwareRequestFilter提供標準的 Servlet API 安全性方法,使用存取 SecurityContext 的請求包裝器。如果您不需要這些方法,可以省略此篩選器。SessionManagementFilter負責在使用者在當前請求期間通過驗證時(例如,通過記住我驗證)應用與會話相關的策略。在其預設組態中,它將建立一個新會話(從現有會話複製屬性),目的是更改會話識別符,並提供針對會話固定攻擊的防禦。當正在使用 Spring Security 的並發會話控制時,也會使用它。在此組態中,UsernamePasswordAuthenticationFilter是唯一的驗證機制,並且也注入了SessionFixationProtectionStrategy。這表示我們可以安全地移除會話管理篩選器。
如果您一直密切關注,您會注意到我們仍然缺少上述組態中的 Bean 參考。安全性攔截器需要使用AccessDecisionManager進行組態。如果您正在使用命名空間,則會在內部建立一個,儘管您也可以插入自訂 Bean。如果沒有命名空間,我們需要明確提供一個。命名空間內部版本的等效項將如下所示
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>
這是由命名空間註冊的另一個 Bean,即使它不是直接必需的(它可能在某些 JSP 標籤中使用)。它允許您查詢目前使用者是否被允許調用特定的 URL。它在您的控制器 Bean 中可能很有用,以建立在呈現的視圖中應該提供哪些資訊或導航連結。
<bean id="webPrivilegeEvaluator" class="org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator">
<constructor-arg ref="filterSecurityInterceptor" />
</bean>
再次說明,本文並非旨在詳細解釋所有這些 Bean 的工作方式,而主要是提供一個參考,以幫助從基本的命名空間組態轉移,並了解底層的內容。如您所見,它非常複雜!但是請記住,可以將相當多的這些 Bean 插入到命名空間組態本身中,而且您現在可以看到它們實際上要去哪裡。現在您已經知道涉及哪些類別,您就知道在哪裡可以找到 Spring Security 參考手冊、Javadoc 以及當然還有原始碼本身的更多資訊。