WebFlux 環境的跨站請求偽造 (CSRF)

本節討論 Spring Security 對 WebFlux 環境的 跨站請求偽造 (CSRF) 支援。

使用 Spring Security CSRF 保護

以下概述使用 Spring Security CSRF 保護的步驟

使用適當的 HTTP 動詞

防止 CSRF 攻擊的第一步是確保您的網站使用適當的 HTTP 動詞。這在 安全方法必須為唯讀 中詳細說明。

設定 CSRF 保護

下一步是在您的應用程式中設定 Spring Security 的 CSRF 保護。預設情況下,Spring Security 的 CSRF 保護是啟用的,但您可能需要自訂組態。接下來的幾個小節將涵蓋一些常見的自訂設定。

自訂 CsrfTokenRepository

預設情況下,Spring Security 使用 WebSessionServerCsrfTokenRepository 將預期的 CSRF 權杖儲存在 WebSession 中。有時,您可能需要設定自訂的 ServerCsrfTokenRepository。例如,您可能想要將 CsrfToken 持久儲存在 Cookie 中,以支援基於 JavaScript 的應用程式

預設情況下,CookieServerCsrfTokenRepository 寫入名為 XSRF-TOKEN 的 Cookie,並從名為 X-XSRF-TOKEN 的標頭或 HTTP _csrf 參數中讀取它。這些預設值來自 AngularJS

您可以在 Java 組態中設定 CookieServerCsrfTokenRepository

將 CSRF 權杖儲存在 Cookie 中
  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		// ...
		.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        // ...
        csrf {
            csrfTokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse()
        }
    }
}

前面的範例明確設定了 cookieHttpOnly=false。這是必要的,以便讓 JavaScript (在本例中為 AngularJS) 讀取它。如果您不需要直接使用 JavaScript 讀取 Cookie 的能力,我們建議省略 cookieHttpOnly=false (改為使用 new CookieServerCsrfTokenRepository()),以提高安全性。

停用 CSRF 保護

預設情況下,CSRF 保護是啟用的。但是,如果 對您的應用程式有意義,您可以停用 CSRF 保護。

以下 Java 組態將停用 CSRF 保護。

停用 CSRF 組態
  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		// ...
		.csrf(csrf -> csrf.disable()))
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        // ...
        csrf {
            disable()
        }
    }
}

設定 ServerCsrfTokenRequestHandler

Spring Security 的 CsrfWebFilter 公開了 Mono<CsrfToken> 作為名為 org.springframework.security.web.server.csrf.CsrfTokenServerWebExchange 屬性,並藉助 ServerCsrfTokenRequestHandler。在 5.8 版中,預設實作是 ServerCsrfTokenRequestAttributeHandler,它只是將 Mono<CsrfToken> 作為交換屬性提供。

從 6.0 版開始,預設實作是 XorServerCsrfTokenRequestAttributeHandler,它為 BREACH 提供保護 (請參閱 gh-4001)。

如果您希望停用 CsrfToken 的 BREACH 保護並還原為 5.8 版預設值,您可以使用以下 Java 組態設定 ServerCsrfTokenRequestAttributeHandler

停用 BREACH 保護
  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		// ...
		.csrf(csrf -> csrf
			.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
		)
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        // ...
        csrf {
            csrfTokenRequestHandler = ServerCsrfTokenRequestAttributeHandler()
        }
    }
}

包含 CSRF 權杖

為了使 同步器權杖模式 能夠防止 CSRF 攻擊,我們必須在 HTTP 請求中包含實際的 CSRF 權杖。它必須包含在請求的一部分中 (表單參數、HTTP 標頭或其他選項),而該部分不會自動包含在瀏覽器的 HTTP 請求中。

我們已經看到Mono<CsrfToken> 作為 ServerWebExchange 屬性公開。這表示任何檢視技術都可以存取 Mono<CsrfToken>,以將預期的權杖公開為表單meta 標籤

如果您的檢視技術沒有提供一種簡單的方法來訂閱 Mono<CsrfToken>,一種常見的模式是使用 Spring 的 @ControllerAdvice 直接公開 CsrfToken。以下範例將 CsrfToken 放在 Spring Security 的 CsrfRequestDataValueProcessor 使用的預設屬性名稱 (_csrf) 上,以自動將 CSRF 權杖作為隱藏輸入包含在內

CsrfToken 作為 @ModelAttribute
  • Java

  • Kotlin

@ControllerAdvice
public class SecurityControllerAdvice {
	@ModelAttribute
	Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
		Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
		return csrfToken.doOnSuccess(token -> exchange.getAttributes()
				.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
	}
}
@ControllerAdvice
class SecurityControllerAdvice {
    @ModelAttribute
    fun csrfToken(exchange: ServerWebExchange): Mono<CsrfToken> {
        val csrfToken: Mono<CsrfToken>? = exchange.getAttribute(CsrfToken::class.java.name)
        return csrfToken!!.doOnSuccess { token ->
            exchange.attributes[CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME] = token
        }
    }
}

幸運的是,Thymeleaf 提供了 整合,無需任何額外工作即可運作。

表單 URL 編碼

若要發佈 HTML 表單,CSRF 權杖必須作為隱藏輸入包含在表單中。以下範例顯示了呈現的 HTML 可能的外觀

CSRF 權杖 HTML
<input type="hidden"
	name="_csrf"
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>

接下來,我們將討論將 CSRF 權杖作為隱藏輸入包含在表單中的各種方法。

自動包含 CSRF 權杖

Spring Security 的 CSRF 支援透過其 CsrfRequestDataValueProcessor 提供與 Spring 的 RequestDataValueProcessor 的整合。為了使 CsrfRequestDataValueProcessor 能夠運作,必須訂閱 Mono<CsrfToken>,並且 CsrfToken 必須作為與 DEFAULT_CSRF_ATTR_NAME 相符的屬性公開。

幸運的是,Thymeleaf 負責處理所有樣板程式碼,透過與 RequestDataValueProcessor 整合,確保具有不安全 HTTP 方法 (POST) 的表單會自動包含實際的 CSRF 權杖。

CsrfToken 請求屬性

如果 包含請求中實際 CSRF 權杖的其他選項不起作用,您可以利用 Mono<CsrfToken> 作為名為 org.springframework.security.web.server.csrf.CsrfTokenServerWebExchange 屬性公開的事實。

以下 Thymeleaf 範例假設您在名為 _csrf 的屬性上公開CsrfToken

表單中具有請求屬性的 CSRF 權杖
<form th:action="@{/logout}"
	method="post">
<input type="submit"
	value="Log out" />
<input type="hidden"
	th:name="${_csrf.parameterName}"
	th:value="${_csrf.token}"/>
</form>

Ajax 和 JSON 請求

如果您使用 JSON,則無法在 HTTP 參數中提交 CSRF 權杖。相反地,您可以在 HTTP 標頭中提交權杖。

在以下各節中,我們將討論在基於 JavaScript 的應用程式中將 CSRF 權杖作為 HTTP 請求標頭包含在內的各種方法。

自動包含

您可以設定 Spring Security,將預期的 CSRF 權杖儲存在 Cookie 中。透過將預期的 CSRF 儲存在 Cookie 中,JavaScript 框架 (例如 AngularJS) 會自動將實際的 CSRF 權杖包含在 HTTP 請求標頭中。

Meta 標籤

除了在 Cookie 中公開 CSRF 之外的另一種模式,是在您的 meta 標籤中包含 CSRF 權杖。HTML 可能看起來像這樣

CSRF meta 標籤 HTML
<html>
<head>
	<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
	<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
	<!-- ... -->
</head>
<!-- ... -->

一旦 meta 標籤包含 CSRF 權杖,JavaScript 程式碼就可以讀取 meta 標籤並將 CSRF 權杖作為標頭包含在內。如果您使用 jQuery,可以使用以下程式碼讀取 meta 標籤

AJAX 傳送 CSRF 權杖
$(function () {
	var token = $("meta[name='_csrf']").attr("content");
	var header = $("meta[name='_csrf_header']").attr("content");
	$(document).ajaxSend(function(e, xhr, options) {
		xhr.setRequestHeader(header, token);
	});
});

以下範例假設您在名為 _csrf 的屬性上公開CsrfToken。以下範例使用 Thymeleaf 執行此操作

CSRF meta 標籤 JSP
<html>
<head>
	<meta name="_csrf" th:content="${_csrf.token}"/>
	<!-- default header name is X-CSRF-TOKEN -->
	<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
	<!-- ... -->
</head>
<!-- ... -->

CSRF 考量

在實作防止 CSRF 攻擊的保護時,有一些特殊的考量事項需要考慮。本節討論與 WebFlux 環境相關的這些考量事項。如需更全面的討論,請參閱 CSRF 考量

登入

您應該要求登入請求使用 CSRF,以防止偽造的登入嘗試。Spring Security 的 WebFlux 支援會自動執行此操作。

登出

您應該要求登出請求使用 CSRF,以防止偽造的登出嘗試。預設情況下,Spring Security 的 LogoutWebFilter 僅處理 HTTP post 請求。這確保登出需要 CSRF 權杖,並且惡意使用者無法強行登出您的使用者。

最簡單的方法是使用表單登出。如果您真的想要連結,可以使用 JavaScript 讓連結執行 POST (可能在隱藏表單上)。對於 JavaScript 已停用的瀏覽器,您可以選擇讓連結將使用者帶到執行 POST 的登出確認頁面。

如果您真的想要將 HTTP GET 用於登出,您可以這樣做,但請記住,通常不建議這樣做。例如,以下 Java 組態會在以任何 HTTP 方法請求 /logout URL 時登出

使用 HTTP GET 登出
  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		// ...
		.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
        // ...
        logout {
            requiresLogout = PathPatternParserServerWebExchangeMatcher("/logout")
        }
    }
}

CSRF 和 Session 超時

預設情況下,Spring Security 將 CSRF 權杖儲存在 WebSession 中。這種安排可能會導致 Session 過期的情況,這表示沒有預期的 CSRF 權杖可供驗證。

我們已經討論了 Session 超時的 一般解決方案。本節討論與 WebFlux 支援相關的 CSRF 超時的具體細節。

您可以將預期 CSRF 權杖的儲存變更為 Cookie。如需詳細資訊,請參閱 自訂 CsrfTokenRepository 節。

Multipart (檔案上傳)

我們已經討論過,保護 multipart 請求 (檔案上傳) 免受 CSRF 攻擊會導致 雞生蛋蛋生雞 的問題。本節討論如何在 WebFlux 應用程式中實作將 CSRF 權杖放置在主體URL 中。

如需有關將 multipart 表單與 Spring 搭配使用的詳細資訊,請參閱 Spring 參考文件的 Multipart 資料 節。

將 CSRF 權杖放置在主體中

我們已經討論過將 CSRF 權杖放置在主體中的權衡取捨。

在 WebFlux 應用程式中,您可以使用以下組態執行此操作

啟用從 multipart/form-data 取得 CSRF 權杖
  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
	http
		// ...
		.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
	return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http {
		// ...
        csrf {
            tokenFromMultipartDataEnabled = true
        }
    }
}

在 URL 中包含 CSRF 權杖

我們已經討論過將 CSRF 權杖放置在 URL 中的權衡取捨。由於 CsrfToken 作為 ServerHttpRequest 請求屬性公開,因此我們可以使用它來建立在其中包含 CSRF 權杖的 action。以下顯示 Thymeleaf 的範例

Action 中的 CSRF 權杖
<form method="post"
	th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
	enctype="multipart/form-data">

HiddenHttpMethodFilter

我們已經討論過覆寫 HTTP 方法。

在 Spring WebFlux 應用程式中,覆寫 HTTP 方法是透過使用 HiddenHttpMethodFilter 來完成的。