領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多隨著 Spring Security 2 中安全模式的引入,搭建一個簡單的安全應用程式變得容易了許多。在舊版本中,使用者必須單獨宣告和連線所有實現 Bean,導致 Spring 應用程式上下文檔案龐大而複雜,難以理解和維護。學習曲線相當陡峭,我至今仍記得,當我於 2004 年開始從事該專案(當時的 Acegi Security)時,花了一段時間才弄明白這一切。從積極的一面來看,這種對框架基本構建塊的接觸意味著,一旦你成功地組裝了一個可用的配置,就幾乎不可能不瞭解重要的類以及它們如何協同工作。反過來,這些知識讓你能夠充分利用定製的機會,而這正是使用 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 時代的人來說,是時候回憶一下了。
我們首先看
<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>
在此階段,我們保留了 `
<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,它純粹是為了支援下面 Web 配置中出現的AnonymousAuthenticationFiter。
<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 等是應用程式上下文中實現javax.servlet.Filter介面的其他 Bean 的名稱。
所以FilterChainProxy將傳入請求與過濾器列表匹配,並透過找到的第一個匹配鏈傳遞請求。請注意,除了 "/images/*" 模式(它對映到一個空的過濾器鏈)之外,這些與
由於上述配置有點冗長,因此可以使用更緊湊的名稱空間語法來配置一個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),清除安全上下文並使會話無效。AnonymousAuthenticationFilter負責為匿名使用者填充安全上下文,從而更容易應用預設安全限制,這些限制在某些 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 以及原始碼本身中查詢更多資訊。