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
-
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()
}
}
}
前面的範例明確設定了 |
停用 CSRF 保護
預設情況下,CSRF 保護是啟用的。但是,如果 對您的應用程式有意義,您可以停用 CSRF 保護。
以下 Java 組態將停用 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.CsrfToken
的 ServerWebExchange
屬性,並藉助 ServerCsrfTokenRequestHandler
。在 5.8 版中,預設實作是 ServerCsrfTokenRequestAttributeHandler
,它只是將 Mono<CsrfToken>
作為交換屬性提供。
從 6.0 版開始,預設實作是 XorServerCsrfTokenRequestAttributeHandler
,它為 BREACH 提供保護 (請參閱 gh-4001)。
如果您希望停用 CsrfToken
的 BREACH 保護並還原為 5.8 版預設值,您可以使用以下 Java 組態設定 ServerCsrfTokenRequestAttributeHandler
-
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 可能的外觀
<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.CsrfToken
的 ServerWebExchange
屬性公開的事實。
以下 Thymeleaf 範例假設您在名為 _csrf
的屬性上公開了 CsrfToken
<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 請求標頭包含在內的各種方法。
Meta 標籤
除了在 Cookie 中公開 CSRF 之外的另一種模式,是在您的 meta
標籤中包含 CSRF 權杖。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 標籤
$(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 執行此操作
<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 時登出
-
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 應用程式中,您可以使用以下組態執行此操作
-
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
}
}
}
HiddenHttpMethodFilter
我們已經討論過覆寫 HTTP 方法。
在 Spring WebFlux 應用程式中,覆寫 HTTP 方法是透過使用 HiddenHttpMethodFilter
來完成的。