知識庫 / REST RSS 訂閱

Spring Security 中 REST 服務的基礎和摘要身份驗證

REST,Spring Security
HongKong
11
04:12 AM · Dec 06 ,2025

目錄

1. 概述

本文討論瞭如何在同一URI結構的REST API上配置基本認證和摘要認證。在之前的文章中,我們討論了另一種安全REST服務的方案——基於表單的認證,因此基本認證和摘要認證是自然的替代方案,同時也是更符合RESTful原則的方案。

2. 基本身份驗證配置使用基於表單身份驗證的根本原因是 Spring Security 會 利用會話 – 這當然是服務器端的狀態,因此 REST 的無狀態約束 在實踐中被忽略了。

我們首先將設置基本身份驗證 – 首先我們從主 <http> 安全元素中刪除舊的自定義入口點和過濾器:

<http create-session="stateless">
   <intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" />

   <http-basic />
</http>

請注意,基本身份驗證的支持已通過單行配置添加,即 ,它處理了 的創建和配置。

<h3><strong>2.1. 滿足無狀態約束——消除會話</strong></h3
><p>RESTful 架構風格的主要約束之一是客户端與服務器之間的通信是完全<strong title="無狀態">無狀態</strong>的,正如 <a title="Representational State Transfer (REST) (第5章)" href="https://web.archive.org/web/20210513160155/https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm" target="_blank" rel="nofollow noopener noreferrer">原始論文</strong>所述:</p>
<blockquote title="無狀態">
 <p><strong title="無狀態">5.1.3 無狀態</strong></p>
 <p>我們為客户端與服務器之間的交互添加了一個約束:通信必須是無狀態的,就像客户端-無狀態-服務器 (CSS) 風格(圖 5-3)那樣,即每個客户端到服務器的請求必須包含理解請求所需的所有信息,並且不能利用服務器上任何存儲的上下文。因此,<strong title="會話狀態">會話狀態</strong>完全保留在客户端。</p>
</blockquote
><p>在 Spring Security 中,<strong title="會話">會話</strong>的概念有着悠久的歷史,直到現在,完全消除它一直很困難,尤其是在使用命名空間進行配置時。</p>
<p>但是,Spring Security 通過在命名空間配置中添加一個 <strong title="會話">會話</strong>選項來增強配置,該選項是 <em title="會話">無會話</em>的,從而有效地保證不會創建或使用任何會話,由 Spring 使用。這個新選項實際上從安全過濾器鏈中完全移除了所有與會話相關的過濾器,從而確保每個請求都進行身份驗證。</p>

3. 摘要認證配置從之前的配置開始,將定義用於設置摘要認證的過濾器和入口點作為 Bean。然後,摘要入口點 將在幕後覆蓋由 創建的入口點。最後,將通過在安全過濾器鏈中使用 語義,引入自定義 摘要過濾器,將其直接放置在基本認證過濾器之後。

<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>

不幸的是,安全命名空間中沒有 支持,以自動配置 digest 身份驗證的方式,就像基本身份驗證可以通過 <http-basic> 進行配置一樣。因此,必要的 Bean 必須手動定義並注入到安全配置中。

4. 在同一 RESTful 服務中同時支持身份驗證協議

使用 Basic 或 Digest 身份驗證協議在 Spring Security 中很容易實現;但同時在同一 RESTful Web 服務上,通過相同的 URI 映射支持這兩個協議,則會增加服務配置和測試的複雜性。

4.1. 匿名請求

在安全鏈中,基本過濾器和摘要過濾器共同作用,當一個匿名請求——即不包含任何身份驗證憑據(Authorization HTTP 標頭)的請求,被 Spring Security 處理時,這兩個身份驗證過濾器會發現無憑據,並繼續執行過濾器鏈的執行。 鑑於請求未經過身份驗證,則會拋出AccessDeniedException,並在ExceptionTranslationFilter中捕獲,從而啓動摘要入口點,並提示客户端提供憑據。

基本過濾器和摘要過濾器的職責非常有限——如果它們無法識別請求中的身份驗證憑據類型,它們將繼續執行安全過濾器鏈的執行。 這正是 Spring Security 能夠配置為在同一 URI 上支持多種身份驗證協議的原因。

當請求包含正確的身份驗證憑據——無論是基本還是摘要協議時,該協議將被正確地使用。 但是,對於匿名請求,客户端只會提示提供摘要身份驗證憑據。 這是因為摘要入口點被配置為 Spring Security 鏈中的主要和唯一入口點;因此,摘要身份驗證可以被認為是默認的

4.2. 使用身份驗證憑據進行請求

一個帶有身份驗證憑據的請求,對於基本身份驗證而言,將通過Authorization標頭以“Basic”前綴開頭的身份驗證方式進行識別。在處理此類請求時,憑據將在基本身份驗證過濾器中進行解碼,並對請求進行授權。同樣,帶有身份驗證憑據的請求,用於散式身份驗證,也將使用“Digest”前綴用於其Authorization標頭。

5. 測試兩種場景

測試將通過使用基本或 digest 身份驗證方式創建新資源來消耗 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. 結論

本文介紹了基本認證和摘要認證對 RESTful 服務配置和實現的全部內容,主要利用 Spring Security 命名空間支持以及框架中的一些新特性。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.