跳到主要内容
版本:7.0.2

WebFlux 环境下的跨站请求伪造(CSRF)

DeepSeek V3 中英对照 CSRF Cross Site Request Forgery (CSRF) for WebFlux Environments

本节讨论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

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

前面的示例显式设置了 cookieHttpOnly=false。这是为了让 JavaScript(在本例中是 AngularJS)能够读取该 cookie。如果您不需要直接用 JavaScript 读取 cookie 的能力,我们建议省略 cookieHttpOnly=false(通过使用 new CookieServerCsrfTokenRepository() 替代),以提高安全性。

禁用 CSRF 保护

默认情况下,CSRF 保护是启用的。不过,如果对你的应用有意义,你也可以禁用 CSRF 保护。

以下 Java 配置将禁用 CSRF 保护。

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

配置 ServerCsrfTokenRequestHandler

Spring Security 的 CsrfWebFilter 借助 ServerCsrfTokenRequestHandler,将 Mono<CsrfToken> 作为名为 org.springframework.security.web.server.csrf.CsrfTokenServerWebExchange 属性暴露出来。在 5.8 版本中,默认实现是 ServerCsrfTokenRequestAttributeHandler,它只是简单地将 Mono<CsrfToken> 作为交换属性提供。

自6.0版本起,默认实现为 XorServerCsrfTokenRequestAttributeHandler,该实现提供了针对 BREACH 攻击的防护(参见 gh-4001)。

若希望禁用 CsrfToken 的 BREACH 保护并恢复至 5.8 版本的默认行为,可通过以下 Java 配置来配置 ServerCsrfTokenRequestAttributeHandler

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

包含 CSRF 令牌

对于同步令牌模式来说,要有效防御 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。该令牌必须包含在请求的某个部分(如表单参数、HTTP 头部或其他选项)中,且该部分不会被浏览器自动包含在 HTTP 请求内。

我们已经看到Mono<CsrfToken> 作为 ServerWebExchange 属性公开。这意味着任何视图技术都可以访问 Mono<CsrfToken>,以将预期的令牌作为表单元标签公开。

如果你的视图技术没有提供订阅 Mono<CsrfToken> 的简便方法,一种常见的模式是使用 Spring 的 @ControllerAdvice 直接暴露 CsrfToken。以下示例将 CsrfToken 放置在 Spring Security 的 CsrfRequestDataValueProcessor 所使用的默认属性名称(_csrf)上,以自动将 CSRF 令牌作为隐藏输入包含在内:

@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));
}
}

幸运的是,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.CsrfTokenServerWebExchange 属性被暴露这一事实。

以下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请求头包含在内的多种方法。

自动包含

您可以配置 Spring Security 将预期的 CSRF 令牌存储在 cookie 中。通过将预期的 CSRF 令牌存储在 cookie 中,JavaScript 框架(例如 AngularJS)会自动将实际的 CSRF 令牌包含在 HTTP 请求头中。

Meta 标签

另一种替代在cookie中暴露CSRF的模式是将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 方法)执行登出:

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

CSRF 与会话超时

默认情况下,Spring Security 将 CSRF 令牌存储在 WebSession 中。这种安排可能导致会话过期的情况,这意味着没有预期的 CSRF 令牌可供验证。

我们已经讨论过通用解决方案来处理会话超时问题。本节将专门讨论 CSRF 超时在 WebFlux 支持中的具体细节。

您可以将预期的CSRF令牌存储方式更改为存储在cookie中。详情请参阅自定义CsrfTokenRepository部分。

多部分(文件上传)

我们已经讨论过如何保护多部分请求(文件上传)免受 CSRF 攻击会导致一个先有鸡还是先有蛋的问题。本节将讨论如何在 WebFlux 应用程序中实现将 CSRF 令牌放置在请求体URL中。

备注

有关在 Spring 中使用多部分表单的更多信息,请参阅 Spring 参考文档中的 Multipart Data 部分。

将 CSRF 令牌置于请求体中

我们已经讨论过将 CSRF 令牌放在请求体中的权衡。

在WebFlux应用中,可以通过以下配置实现:

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

在URL中包含CSRF令牌

我们已经讨论过将 CSRF 令牌置于 URL 中的权衡。由于 CsrfToken 作为 ServerHttpRequest 请求属性公开,我们可以利用它来创建一个包含 CSRF 令牌的 action。以下是一个使用 Thymeleaf 的示例:

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

HiddenHttpMethodFilter

我们已经讨论过如何重写 HTTP 方法。

在 Spring WebFlux 应用中,通过使用 HiddenHttpMethodFilter 来覆盖 HTTP 方法。