跳到主要内容
版本:6.4.3

跨站请求伪造 (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();
}
备注

前面的示例显式设置了 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();
}

配置 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:

@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>,并将预期的令牌作为 表单 或 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));
}
}

幸运的是,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 请求头包含在内的各种方法。

自动包含​

你可以配置 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>
<!-- ... -->

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

var token = $('meta[name="csrf-token"]').attr('content');
$(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 时执行注销操作:

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

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

在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 方法。