跳到主要内容

跨站请求伪造 (CSRF) 针对 WebFlux 环境

QWen Max 中英对照 CSRF Cross Site Request Forgery (CSRF) for WebFlux Environments

本节讨论 Spring Security 的跨站请求伪造 (CSRF)在 WebFlux 环境中的支持。

使用 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();
}
java
备注

前面的示例显式设置了 cookieHttpOnly=false。这是为了让 JavaScript(在这种情况下是 AngularJS)能够读取它。如果您不需要直接使用 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();
}
java

配置 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();
}
java

包含 CSRF 令牌

为了使用同步令牌模式来防范CSRF攻击,我们必须在HTTP请求中包含实际的CSRF令牌。它必须被包含在请求的一部分(如表单参数、HTTP头部或其他选项)中,而这一部分不会由浏览器自动包含在HTTP请求中。

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

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

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

幸运的是,Thymeleaf 提供了集成,无需任何额外工作即可使用。

表单 URL 编码

要提交一个 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。以下示例展示了渲染后的 HTML 可能的样子:

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

接下来,我们讨论将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 示例假设你暴露 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>
html

Ajax 和 JSON 请求

如果你使用 JSON,你不能在 HTTP 参数中提交 CSRF 令牌。相反,你可以在 HTTP 头中提交该令牌。

在接下来的部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求头包含在内的各种方法。

自动包含

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

Meta 标签

将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>
<!-- ... -->
html

一旦 meta 标签中包含 CSRF 令牌,JavaScript 代码就可以读取这些 meta 标签并将 CSRF 令牌作为头部包含进去。如果你使用 jQuery,你可以用以下代码读取 meta 标签:

var token = $('meta[name="csrf-token"]').attr('content');
javascript
$(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);
});
});
javascript

以下示例假设你暴露 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>
<!-- ... -->
html

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 时执行注销操作:

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

CSRF 和会话超时

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

我们已经讨论了通用解决方案来处理会话超时。本节讨论与 WebFlux 支持相关的 CSRF 超时的具体情况。

您可以将预期的CSRF令牌存储在cookie中。有关详细信息,请参阅Custom CsrfTokenRepository部分。

多部分(文件上传)

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

备注

有关使用 Spring 处理多部分表单的更多信息,请参阅 Spring 参考文档中的 多部分数据 部分。

将CSRF令牌放在Body中

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

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

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

在URL中包含CSRF令牌

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

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

HiddenHttpMethodFilter

我们已经讨论过覆盖 HTTP 方法。

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