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 藉助 ServerCsrfTokenRequestHandler 將 Mono<CsrfToken> 作為 ServerWebExchange 屬性(名為 org.springframework.security.web.server.csrf.CsrfToken)公開。在 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>,從而將預期令牌作為 表單 或 元標記 公開。
如果您的檢視技術不提供訂閱 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) -> token.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 必須 作為屬性公開,該屬性與 CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME 匹配。
幸運的是,Thymeleaf 為您處理了所有樣板檔案,透過與 RequestDataValueProcessor 整合,確保使用不安全 HTTP 方法 (POST) 的表單自動包含實際的 CSRF 令牌。
CsrfToken 請求屬性
如果將實際 CSRF 令牌包含在請求中的 其他選項 不起作用,您可以利用 Mono<CsrfToken> 作為 名為 org.springframework.security.web.server.csrf.CsrfToken 的 ServerWebExchange 屬性的事實。
以下 Thymeleaf 示例假設您將 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 請求標頭包含在內的各種方法。
元標記
將 CSRF 公開在 cookie 中 的另一種模式是將 CSRF 令牌包含在您的 meta 標記中。HTML 可能看起來像這樣
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
一旦元標記包含 CSRF 令牌,JavaScript 程式碼就可以讀取元標記並將 CSRF 令牌作為標頭包含在內。如果您使用 jQuery,可以使用以下程式碼讀取元標記
$(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);
});
});
以下示例假設您將 CsrfToken 公開 為名為 _csrf 的屬性。以下示例使用 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 配置在請求 /logout URL 時(使用任何 HTTP 方法)登出
-
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 和會話超時
預設情況下,Spring Security 將 CSRF 令牌儲存在 WebSession 中。這種安排可能導致會話過期的情況,這意味著沒有預期的 CSRF 令牌可供驗證。
我們已經討論了 解決會話超時的一般方案。本節討論與 WebFlux 支援相關的 CSRF 超時的具體細節。
您可以將會話 CSRF 令牌的儲存方式更改為 cookie。有關詳細資訊,請參閱 自定義 CsrfTokenRepository 部分。
多部分 (檔案上傳)
|
有關在 Spring 中使用多部分表單的更多資訊,請參閱 Spring 參考文件的 多部分資料 部分。 |
將 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 完成的。