Table of Contents
- 1.Overview
- 2.Configuration of Basic Authentication
- 2.1.Satisfying the stateless constraint – getting rid of sessions
- 3.Configuration of Digest Authentication
- 4.Supporting both authentication protocols in the same RESTful service
- 4.1.Anonymous request
- 4.2.Request with authentication credentials
- 5.Testing both scenarios
- 6.Conclusion
1. 概述
本文討論瞭如何在同一URI結構的REST API上設置基本身份驗證和摘要身份驗證的方法。 在一篇之前的文章中,我們討論了另一種保護REST服務的安全方法——基於表單的身份驗證,因此基本身份驗證和摘要身份驗證是自然的替代方案,也是更符合RESTful原則的方案。
基本身份驗證配置
使用表單身份驗證作為 RESTful 服務的主要原因是不理想,因為 Spring Security 會 利用會話——這顯然是服務器端的狀態,因此 REST 的無狀態約束實際上被忽略了。
我們將首先設置基本身份驗證——首先我們從主 安全元素中刪除舊的自定義入口點和過濾器:
<http create-session="stateless">
<intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" />
<http-basic />
</http>
請注意,使用基本身份驗證只需要添加一行配置 ,即可處理 基本身份驗證過濾器 和 基本身份驗證入口點 的創建和連接。
滿足無狀態約束——消除會話
RESTful 架構風格的主要約束之一是客户端與服務器之間的通信是完全 無狀態的,正如原始論文所讀的:
5.1.3 無狀態服務器端 會話 概念在 Spring Security 中有着悠久的歷史,直到現在,完全消除它非常困難,尤其是在使用命名空間進行配置時。我們接下來為客户端與服務器之間的交互添加一個約束:通信必須是無狀態的,就像在客户端-無狀態-服務器 (CSS) 樣式中,如第 3.4.3 節的圖 5-3 所示,以便每個客户端到服務器的請求都必須包含理解請求所需的所有信息,並且不能利用服務器上任何存儲的上下文。 因此會話狀態完全保留在客户端。
但是,Spring Security 通過使用新的 無狀態選項 來增強命名空間配置,從而有效地保證不會創建或使用任何會話,Spring 完成了所有與會話相關的過濾器的移除,從而確保身份驗證對每個請求進行執行。
3. 摘要認證配置
從之前的配置開始,摘要認證所需的過濾器和入口點將定義為 Bean。然後,摘要入口點將覆蓋由<http-basic>在後台創建的那個。最後,自定義摘要過濾器將在安全過濾器鏈中使用after語義,將其直接放置在基本認證過濾器之後。
<http create-session="stateless" entry-point-ref="digestEntryPoint">
<intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" />
<http-basic />
<custom-filter ref="digestFilter" after="BASIC_AUTH_FILTER" />
</http>
<beans:bean id="digestFilter" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<beans:property name="userDetailsService" ref="userService" />
<beans:property name="authenticationEntryPoint" ref="digestEntryPoint" />
</beans:bean>
<beans:bean id="digestEntryPoint" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<beans:property name="realmName" value="Contacts Realm via Digest Authentication"/>
<beans:property name="key" value="acegi" />
</beans:bean>
<authentication-manager>
<authentication-provider>
<user-service id="userService">
<user name="eparaschiv" password="eparaschiv" authorities="ROLE_ADMIN" />
<user name="user" password="user" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
不幸的是,在安全命名空間中沒有支持的方式,可以像配置基本認證那樣自動配置摘要認證。因此,必須定義並手動將必要的 Bean 注入到安全配置中。
4. 支持同一 RESTful 服務中同時使用兩種身份驗證協議
基本或 Digest 身份驗證可以輕鬆地在 Spring Security 中實現;同時,它們也支持在同一 RESTful Web 服務上的相同 URI 映射中同時使用,這為服務的配置和測試引入了新的複雜性。
4.1. 匿名請求
當基本和 Digest 過濾器在安全鏈中時,一個 匿名請求——一個不包含身份驗證憑據 (Authorization HTTP 標頭) 的請求——由 Spring Security 處理的方式是——兩個身份驗證過濾器將找不到 憑據,並且將繼續執行過濾器鏈的執行。 鑑於請求未被認證,因此會拋出 AccessDeniedException,並在 ExceptionTranslationFilter 中捕獲,從而啓動 Digest 入口點,提示客户端提供憑據。
基本和 Digest 過濾器的責任非常狹窄——如果它們無法識別請求中身份驗證憑據的類型,它們將繼續執行安全過濾器鏈。 這正是 Spring Security 具有配置支持同一 URI 上多種身份驗證協議的靈活性所在。
當請求包含正確的身份驗證憑據——基本或 Digest 協議時,該協議將被正確地使用。 但是,對於匿名請求,客户端只會提示進行 Digest 身份驗證憑據提示。 這是因為 Digest 入口點已配置為 Spring Security 鏈中的主要和唯一入口點;因此,Digest 身份驗證可以被認為是默認的。
4.2. 帶有身份驗證憑據的請求
一個用於 Basic 身份驗證的 帶有身份驗證憑據的請求將通過 Authorization 標頭以 “Basic” 格式開頭來識別。 處理此類請求時,憑據將在基本身份驗證過濾器中進行解碼,並且請求將被授權。 同樣,具有用於 Digest 身份驗證的憑據的請求將使用 “Digest” 格式的 Authorization 標頭。
5. 測試兩種場景
測試將通過創建資源來消耗 REST 服務,無論使用基本身份驗證還是摘要身份驗證:
@Test
public void givenAuthenticatedByBasicAuth_whenAResourceIsCreated_then201IsReceived(){
// Given
// When
Response response = given()
.auth().preemptive().basic( ADMIN_USERNAME, ADMIN_PASSWORD )
.contentType( HttpConstants.MIME_JSON ).body( new Foo( randomAlphabetic( 6 ) ) )
.post( paths.getFooURL() );
// Then
assertThat( response.getStatusCode(), is( 201 ) );
}
@Test
public void givenAuthenticatedByDigestAuth_whenAResourceIsCreated_then201IsReceived(){
// Given
// When
Response response = given()
.auth().digest( ADMIN_USERNAME, ADMIN_PASSWORD )
.contentType( HttpConstants.MIME_JSON ).body( new Foo( randomAlphabetic( 6 ) ) )
.post( paths.getFooURL() );
// Then
assertThat( response.getStatusCode(), is( 201 ) );
}
注意,使用基本身份驗證的測試會在請求中預先添加憑據,無論服務器是否已發起身份驗證挑戰。這樣做是為了確保服務器無需對客户端發起憑據挑戰,因為如果服務器進行了挑戰,那將是針對 Digest 憑據,因為 Digest 是默認的。
6. 結論
本文介紹了基本認證和 Digest 認證對 RESTful 服務配置和實現的全部內容,主要使用了 Spring Security 命名空間支持以及框架中的一些新功能。