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()
}
}
}
前面的示例显式设置了 `cookieHttpOnly=false`。这是必要的,以便让 JavaScript(在本例中为 AngularJS)读取它。如果您不需要直接使用 JavaScript 读取 cookie 的功能,我们建议省略 `cookieHttpOnly=false`(改为使用 `new CookieServerCsrfTokenRepository()`),以提高安全性。 |
禁用 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>`作为名为`org.springframework.security.web.server.csrf.CsrfToken`的`ServerWebExchange`属性公开。在 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>
的简单方法,一种常见的模式是使用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示例假设您将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请求头包含的各种方法。
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);
});
});
以下示例假设您将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配置在使用任何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和会话超时
默认情况下,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
来完成的。