安全名稱空間配置

名稱空間配置自 Spring Framework 2.0 版本以來就已可用。它允許您透過額外的 XML Schema 元素補充傳統的 Spring Bean 應用程式上下文語法。您可以在 Spring 參考文件中找到更多資訊。您可以使用名稱空間元素更簡潔地配置單個 Bean,或者更強大地定義一個替代配置語法,該語法更貼合問題領域並向用戶隱藏底層複雜性。一個簡單的元素可以隱藏嚮應用程式上下文新增多個 Bean 和處理步驟的事實。例如,將 security 名稱空間中的以下元素新增到應用程式上下文將啟動一個嵌入式 LDAP 伺服器,用於應用程式內部的測試

<security:ldap-server />

這比配置等效的 Apache Directory Server Bean 要簡單得多。最常見的替代配置要求可以透過 ldap-server 元素上的屬性來支援,使用者無需擔心需要建立哪些 Bean 以及 Bean 屬性名稱是什麼。您可以在 LDAP 認證一章中找到更多關於 ldap-server 元素使用的資訊。在編輯應用程式上下文檔案時,一個好的 XML 編輯器應該能提供可用屬性和元素的資訊。我們建議您嘗試使用 Spring Tool Suite,因為它具有處理標準 Spring 名稱空間的特殊功能。

要開始在您的應用程式上下文中使用 security 名稱空間,請將 spring-security-config jar 新增到您的 classpath 中。然後,您只需要將 Schema 宣告新增到您的應用程式上下文檔案即可

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		https://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans>

在您看到的許多示例(以及示例應用程式)中,我們經常使用 security(而不是 beans)作為預設名稱空間,這意味著我們可以省略所有安全名稱空間元素上的字首,使內容更易於閱讀。如果您將應用程式上下文劃分為單獨的檔案,並且大部分安全配置在一個檔案中,您可能也想這樣做。那麼您的安全應用程式上下文檔案將以如下方式開始

<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
		https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/security
		https://www.springframework.org/schema/security/spring-security.xsd">
	...
</beans:beans>

從本章開始,我們假設使用了這種語法。

名稱空間設計

名稱空間旨在涵蓋框架中最常見的用法,並提供一種簡化和簡潔的語法,以便在應用程式中啟用它們。該設計圍繞框架內的大規模依賴關係構建,可分為以下幾個領域

  • Web/HTTP 安全是最複雜的部分。它設定了用於應用框架認證機制、保護 URL、渲染登入和錯誤頁面等的過濾器和相關的服務 Bean。

  • 業務物件(方法)安全定義了保護服務層的選項。

  • AuthenticationManager 處理來自框架其他部分的認證請求。

  • AccessDecisionManager 為 Web 和方法安全提供訪問決策。預設的 AccessDecisionManager 會被註冊,但您可以選擇使用自定義的,使用常規的 Spring Bean 語法宣告即可。

  • AuthenticationProvider 例項提供了認證管理器用於認證使用者的機制。該名稱空間支援多種標準選項,並提供了使用傳統語法宣告自定義 Bean 的方式。

  • UserDetailsService 與認證提供者密切相關,但也經常被其他 Bean 所需要。

在接下來的章節中,我們將看到如何配置這些內容。

安全名稱空間配置入門

本節將介紹如何構建名稱空間配置以使用框架的一些主要特性。我們假設您最初希望儘快啟動並執行,並向現有 Web 應用程式新增認證支援和訪問控制,包括一些測試登入。然後我們將探討如何切換到針對資料庫或其他安全倉庫進行認證。在後面的章節中,我們將介紹更高階的名稱空間配置選項。

web.xml 配置

您需要做的第一件事是將以下過濾器宣告新增到您的 web.xml 檔案中

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

DelegatingFilterProxy 是一個 Spring Framework 類,它將委託給在應用程式上下文中定義為 Spring Bean 的過濾器實現。在這種情況下,該 Bean 名為 springSecurityFilterChain,它是名稱空間建立的一個內部基礎設施 Bean,用於處理 Web 安全。請注意,您不應自己使用此 Bean 名稱。將此 Bean 新增到您的 web.xml 後,您就可以開始編輯您的應用程式上下文檔案了。Web 安全服務透過 <http> 元素進行配置。

最小化的 <http> 配置

要啟用 Web 安全,您需要以下配置

<http>
<intercept-url pattern="/**" access="hasRole('USER')" />
<form-login />
<logout />
</http>

該列表表示我們希望

  • 我們應用程式中的所有 URL 都受到保護,訪問它們需要 ROLE_USER 角色

  • 使用使用者名稱和密碼透過表單登入到應用程式

  • 註冊一個登出 URL,允許我們從應用程式中登出

<http> 元素是所有 Web 相關名稱空間功能的父元素。<intercept-url> 元素定義了一個 pattern,使用 Ant 路徑語法與傳入請求的 URL 進行匹配。有關實際匹配方式的更多詳細資訊,請參見關於 HttpFirewall 的章節。您也可以選擇使用正則表示式匹配(更多詳細資訊請參見名稱空間附錄)。access 屬性定義了與給定模式匹配的請求的訪問要求。在預設配置下,這通常是一個逗號分隔的角色列表,使用者必須擁有其中一個角色才能被允許發出請求。ROLE_ 字首是一個標記,表示應與使用者的許可權進行簡單比較。換句話說,應使用正常的基於角色的檢查。Spring Security 中的訪問控制不限於使用簡單角色(因此使用字首來區分不同型別的安全屬性)。我們稍後將看到解釋如何變化。access 屬性中逗號分隔值的解釋取決於所使用的 AccessDecisionManager 的實現。自 Spring Security 3.0 起,您還可以使用 EL 表示式填充此屬性。

您可以使用多個 <intercept-url> 元素為不同的 URL 集定義不同的訪問要求,但它們按照列出的順序進行評估,並使用第一個匹配項。因此,您必須將最具體的匹配項放在頂部。您還可以新增 method 屬性,將匹配限制為特定的 HTTP 方法(GETPOSTPUT 等)。

要新增使用者,您可以直接在名稱空間中定義一組測試資料

<authentication-manager>
<authentication-provider>
	<user-service>
	<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
	NoOpPasswordEncoder should be used. This is not safe for production, but makes reading
	in samples easier. Normally passwords should be hashed using BCrypt -->
	<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

前面的列表顯示了一種安全儲存相同密碼的示例。密碼以 {bcrypt} 開頭,指示 DelegatingPasswordEncoder(它支援任何配置的 PasswordEncoder 進行匹配)密碼使用 BCrypt 進行雜湊處理

<authentication-manager>
<authentication-provider>
	<user-service>
	<user name="jimi" password="{bcrypt}$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
			authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{bcrypt}$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
			authorities="ROLE_USER" />
	<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
	<user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
	</user-service>
</authentication-provider>
</authentication-manager>

<http> 元素負責建立 FilterChainProxy 及其使用的過濾器 Bean。以前常見的過濾器順序不正確等問題已不再存在,因為過濾器位置是預定義的。

<authentication-provider> 元素建立 DaoAuthenticationProvider Bean,而 <user-service> 元素建立 InMemoryDaoImpl。所有 authentication-provider 元素都必須是 <authentication-manager> 元素的子元素,後者建立 ProviderManager 並向其註冊認證提供者。您可以在名稱空間附錄中找到關於建立的 Bean 的更詳細資訊。如果您想開始瞭解框架中的重要類及其使用方式,特別是如果您以後想進行定製,應該查閱此附錄。

前面的配置定義了兩個使用者、他們的密碼以及他們在應用程式中的角色(用於訪問控制)。您還可以透過設定 user-service 元素上的 properties 屬性從標準屬性檔案中載入使用者資訊。有關檔案格式的更多詳細資訊,請參閱關於記憶體中認證的章節。使用 <authentication-provider> 元素意味著使用者資訊由認證管理器用於處理認證請求。您可以有多個 <authentication-provider> 元素來定義不同的認證源。每個源都會依次被諮詢。

此時,您應該能夠啟動您的應用程式,並且在繼續之前需要登入。請嘗試執行它,或者嘗試使用專案附帶的“tutorial”示例應用程式進行實驗。

設定預設登入後目的地

如果表單登入不是由嘗試訪問受保護資源觸發的,那麼 default-target-url 選項就會生效。這是使用者成功登入後將被帶到的 URL。它預設為 /。您還可以透過將 always-use-default-target 屬性設定為 true 來配置,使使用者始終到達此頁面(無論登入是“按需”的還是他們明確選擇登入的)。如果您的應用程式始終要求使用者從“首頁”開始,例如,這會很有用

<http pattern="/login.htm*" security="none"/>
<http use-expressions="false">
<intercept-url pattern='/**' access='ROLE_USER' />
<form-login login-page='/login.htm' default-target-url='/home.htm'
		always-use-default-target='true' />
</http>

為了對目的地有更多的控制,您可以使用 authentication-success-handler-ref 屬性作為 default-target-url 的替代方案。引用的 Bean 應該是 AuthenticationSuccessHandler 的一個例項。

高階 Web 特性

本節介紹了一些超出基礎知識的各種特性。

新增自定義過濾器

如果您以前使用過 Spring Security,您就知道該框架維護著一個過濾器鏈,用於應用其服務。您可能想在特定位置向堆疊新增自己的過濾器,或者使用當前沒有名稱空間配置選項的 Spring Security 過濾器(例如 CAS)。或者,您可能想使用標準名稱空間過濾器的定製版本,例如 UsernamePasswordAuthenticationFilter(由 <form-login> 元素建立),以便利用顯式使用 Bean 時可用的額外配置選項。由於過濾器鏈未直接暴露,您如何使用名稱空間配置來實現這一點?

使用名稱空間時,過濾器的順序總是嚴格強制執行。建立應用程式上下文時,過濾器 Bean 由名稱空間處理程式碼排序,並且標準 Spring Security 過濾器在名稱空間中都有一個別名和一個已知位置。

在以前的版本中,排序是在過濾器例項建立後,在應用程式上下文的後處理期間進行的。在 3.0+ 版本中,排序現在是在 Bean 元資料級別進行的,在類例項化之前。這對如何將自己的過濾器新增到堆疊有影響,因為在解析 <http> 元素時必須知道整個過濾器列表,因此在 3.0 中語法略有變化。

建立過濾器的過濾器、別名、名稱空間元素和屬性在下表中顯示,按它們在過濾器鏈中出現的順序排列

表 1. 標準過濾器別名和順序
別名 過濾器類 名稱空間元素或屬性

DISABLE_ENCODE_URL_FILTER

DisableEncodeUrlFilter

http@disable-url-rewriting

FORCE_EAGER_SESSION_FILTER

ForceEagerSessionCreationFilter

http@create-session="ALWAYS"

CHANNEL_FILTER

ChannelProcessingFilter

http/intercept-url@requires-channel

SECURITY_CONTEXT_FILTER

SecurityContextPersistenceFilter

http

CONCURRENT_SESSION_FILTER

ConcurrentSessionFilter

session-management/concurrency-control

HEADERS_FILTER

HeaderWriterFilter

http/headers

CSRF_FILTER

CsrfFilter

http/csrf

LOGOUT_FILTER

LogoutFilter

http/logout

X509_FILTER

X509AuthenticationFilter

http/x509

PRE_AUTH_FILTER

AbstractPreAuthenticatedProcessingFilter 子類

N/A

CAS_FILTER

CasAuthenticationFilter

N/A

FORM_LOGIN_FILTER

UsernamePasswordAuthenticationFilter

http/form-login

BASIC_AUTH_FILTER

BasicAuthenticationFilter

http/http-basic

SERVLET_API_SUPPORT_FILTER

SecurityContextHolderAwareRequestFilter

http/@servlet-api-provision

JAAS_API_SUPPORT_FILTER

JaasApiIntegrationFilter

http/@jaas-api-provision

REMEMBER_ME_FILTER

RememberMeAuthenticationFilter

http/remember-me

ANONYMOUS_FILTER

AnonymousAuthenticationFilter

http/anonymous

SESSION_MANAGEMENT_FILTER

SessionManagementFilter

session-management

EXCEPTION_TRANSLATION_FILTER

ExceptionTranslationFilter

http

FILTER_SECURITY_INTERCEPTOR

FilterSecurityInterceptor

http

SWITCH_USER_FILTER

SwitchUserFilter

N/A

您可以使用 custom-filter 元素以及其中一個名稱來指定您的過濾器應該出現在堆疊中的位置,從而將您的過濾器新增到堆疊中

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>

您還可以使用 afterbefore 屬性,如果您希望將您的過濾器插入到堆疊中另一個過濾器之前或之後。您可以將 FIRSTLASTposition 屬性一起使用,分別表示您希望您的過濾器出現在整個堆疊之前或之後。

避免過濾器位置衝突

如果您插入的自定義過濾器可能與名稱空間建立的標準過濾器之一佔據相同的位置,您不應錯誤地包含名稱空間版本。移除任何建立您想要替換其功能的過濾器的元素。

請注意,您不能替換由 <http> 元素本身建立的過濾器:SecurityContextPersistenceFilterExceptionTranslationFilterFilterSecurityInterceptor。預設情況下,會新增一個 AnonymousAuthenticationFilter,除非您停用 會話固定保護,否則也會向過濾器鏈新增一個 SessionManagementFilter

如果您替換一個需要認證入口點(即認證過程由未認證使用者嘗試訪問受保護資源觸發)的名稱空間過濾器,您也需要新增一個自定義入口點 Bean。

方法安全

自 2.0 版本以來,Spring Security 對向服務層方法新增安全性提供了實質性的支援。它支援 JSR-250 註解安全以及框架原有的 @Secured 註解。自 3.0 版本以來,您還可以使用基於表示式的註解。您可以將安全性應用於單個 Bean(透過使用 intercept-methods 元素裝飾 Bean 宣告),或者使用 AspectJ 風格的切入點保護整個服務層的多個 Bean。

預設 AccessDecisionManager

本節假設您對 Spring Security 內部訪問控制的底層架構有一些瞭解。如果您不瞭解,可以跳過此節稍後再回來看,因為此節僅與需要進行一些定製以使用比簡單基於角色的安全更復雜功能的人員相關。

當您使用名稱空間配置時,會自動為您註冊一個預設的 AccessDecisionManager 例項,並根據您在 intercept-urlprotect-pointcut 宣告中(以及如果您使用註解來保護方法時在註解中)指定的訪問屬性,用於方法呼叫和 Web URL 訪問的決策。

預設策略是使用一個帶有 RoleVoterAuthenticatedVoterAffirmativeBased AccessDecisionManager。您可以在關於授權的章節中找到更多關於這些的資訊。

定製 AccessDecisionManager

如果您需要使用更復雜的訪問控制策略,您可以為方法安全和 Web 安全設定一個替代方案。

對於方法安全,您可以透過將 global-method-security 上的 access-decision-manager-ref 屬性設定為應用程式上下文中相應的 AccessDecisionManager Bean 的 id 來實現

<global-method-security access-decision-manager-ref="myAccessDecisionManagerBean">
...
</global-method-security>

Web 安全的語法相同,但屬性在 http 元素上

<http access-decision-manager-ref="myAccessDecisionManagerBean">
...
</http>