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

使用 Spring Security CSRF 保护

使用 Spring Security 的 CSRF 保护的步骤概述如下:Spring中文文档

使用正确的 HTTP 谓词

防范 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 谓词。 安全方法必须是只读中对此进行了详细介绍。Spring中文文档

配置 CSRF 保护

下一步是在应用程序中配置Spring Security的CSRF保护。 默认情况下,Spring Security 的 CSRF 保护处于启用状态,但您可能需要自定义配置。 接下来的几个小节将介绍一些常见的自定义项。Spring中文文档

自定义 CsrfTokenRepository

默认情况下,Spring Security 将预期的 CSRF 令牌存储在 by using 中。 有时,您可能需要配置自定义 . 例如,您可能希望在 cookie 中保留 ,以支持基于 JavaScript 的应用程序WebSessionWebSessionServerCsrfTokenRepositoryServerCsrfTokenRepositoryCsrfTokenSpring中文文档

默认情况下,写入名为 Cookie 并从名为 HTTP 参数的标头中读取它。 这些默认值来自 AngularJSCookieServerCsrfTokenRepositoryXSRF-TOKENX-XSRF-TOKEN_csrfSpring中文文档

您可以在 Java 配置中配置:CookieServerCsrfTokenRepositorySpring中文文档

将 CSRF 令牌存储在 Cookie 中
@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()
        }
    }
}

前面的示例显式设置了 。 这是让 JavaScript(在本例中为 AngularJS)读取它所必需的。 如果您不需要直接使用 JavaScript 读取 cookie,我们建议省略(改用)以提高安全性。cookieHttpOnly=falsecookieHttpOnly=falsenew CookieServerCsrfTokenRepository()Spring中文文档

禁用 CSRF 保护

默认情况下,CSRF 保护处于启用状态。 但是,如果 CSRF 保护对您的应用程序有意义,则可以禁用它。Spring中文文档

下面的 Java 配置将禁用 CSRF 保护。Spring中文文档

禁用 CSRF 配置
@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 的 CsrfWebFilterMono<CsrfToken>公开为在 ServerCsrfTokenRequestHandler 的帮助下命名的属性。 在 5.8 中,默认实现是 ,它只是将 作为交换属性提供。ServerWebExchangeorg.springframework.security.web.server.csrf.CsrfTokenServerCsrfTokenRequestAttributeHandlerMono<CsrfToken>Spring中文文档

从 6.0 开始,默认实现为 ,它为 BREACH 提供保护(参见 gh-4001)。XorServerCsrfTokenRequestAttributeHandlerSpring中文文档

如果您希望禁用 BREACH 保护并恢复到 5.8 默认值,您可以使用以下 Java 配置进行配置:CsrfTokenServerCsrfTokenRequestAttributeHandlerSpring中文文档

禁用 BREACH 保护
@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 请求中。Spring中文文档

我们已经看到 被公开为一个属性。 这意味着任何视图技术都可以访问以将预期的令牌公开为表单元标记Mono<CsrfToken>ServerWebExchangeMono<CsrfToken>Spring中文文档

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

CsrfToken@ModelAttribute
@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 提供的集成无需任何额外工作即可工作。Spring中文文档

表单 URL 编码

要发布 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。 以下示例显示了呈现的 HTML 可能如下所示:Spring中文文档

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

接下来,我们将讨论将 CSRF 令牌作为隐藏输入包含在表单中的各种方法。Spring中文文档

自动包含 CSRF 代币

Spring Security 的 CSRF 支持通过其 CsrfRequestDataValueProcessor 提供与 Spring 的 RequestDataValueProcessor 的集成。 要工作,必须订阅 并且必须公开为与DEFAULT_CSRF_ATTR_NAME匹配的属性CsrfRequestDataValueProcessorMono<CsrfToken>CsrfTokenSpring中文文档

幸运的是,Thymeleaf 通过集成来为您处理所有样板,以确保具有不安全的 HTTP 方法 (POST) 的表单自动包含实际的 CSRF 令牌。RequestDataValueProcessorSpring中文文档

CsrfToken 请求属性

如果在请求中包含实际 CSRF 令牌的其他选项不起作用,则可以利用 公开为名为 的属性这一事实。Mono<CsrfToken>ServerWebExchangeorg.springframework.security.web.server.csrf.CsrfTokenSpring中文文档

以下 Thymeleaf 示例假定您公开了名为 :CsrfToken_csrfSpring中文文档

具有请求属性的表单中的 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 标头中提交令牌。Spring中文文档

在以下各节中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求标头包含的各种方法。Spring中文文档

自动包含

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

Meta标签

在 Cookie 中公开 CSRF 的另一种模式是在您的标签中包含 CSRF 令牌。 HTML 可能如下所示:metaSpring中文文档

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 标记:Spring中文文档

AJAX 发送 CSRF 令牌
$(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);
	});
});

下面的示例假定您公开名为 的 on 属性。 以下示例对 Thymeleaf 执行此操作:CsrfToken_csrfSpring中文文档

CSRF 元标记 JSP
<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>
<!-- ... -->

前面的示例显式设置了 。 这是让 JavaScript(在本例中为 AngularJS)读取它所必需的。 如果您不需要直接使用 JavaScript 读取 cookie,我们建议省略(改用)以提高安全性。cookieHttpOnly=falsecookieHttpOnly=falsenew CookieServerCsrfTokenRepository()Spring中文文档

CSRF 注意事项

在实施针对 CSRF 攻击的防护时,需要考虑一些特殊注意事项。 本节讨论与 WebFlux 环境相关的这些注意事项。 有关更一般性的讨论,请参阅 CSRF 注意事项Spring中文文档

登录

您应该要求 CSRF 进行登录请求,以防止伪造登录尝试。 Spring Security 的 WebFlux 支持会自动执行此操作。Spring中文文档

注销

您应该要求 CSRF 进行注销请求,以防止伪造注销尝试。 默认情况下,Spring Security 仅处理 HTTP POST 请求。 这可确保注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。LogoutWebFilterSpring中文文档

最简单的方法是使用表单注销。 如果你真的想要一个链接,你可以使用 JavaScript 让链接执行 POST(可能在隐藏的表单上)。 对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到执行 POST 的注销确认页面。Spring中文文档

如果您确实想在注销时使用 HTTP GET,可以这样做,但请记住,通常不建议这样做。 例如,当使用任何 HTTP 方法请求 URL 时,以下 Java 配置将注销:/logoutSpring中文文档

使用 HTTP GET 注销
@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 令牌存储在 . 这种安排可能会导致会话过期的情况,这意味着没有预期的 CSRF 令牌来验证。WebSessionSpring中文文档

我们已经讨论了会话超时的一般解决方案。 本节讨论 CSRF 超时的细节,因为它与 WebFlux 支持有关。Spring中文文档

您可以将预期 CSRF 令牌的存储更改为 cookie 中的存储。 有关详细信息,请参阅自定义 CsrfTokenRepository 部分。Spring中文文档

分片(文件上传)

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

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

将 CSRF 令牌放在正文中

我们已经讨论了将 CSRF 代币放入正文中的权衡。Spring中文文档

在 WebFlux 应用程序中,可以使用以下配置执行此操作:Spring中文文档

启用从 multipart/form-data 获取 CSRF 令牌
@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
        }
    }
}

在 URL 中包含 CSRF 令牌

我们已经讨论了将 CSRF 令牌放在 URL 中的权衡。 由于 是作为请求属性公开的,因此我们可以使用它来创建一个包含 CSRF 令牌的 CSRF 令牌。 百里香叶的示例如下所示:CsrfTokenServerHttpRequestactionSpring中文文档

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

HiddenHttpMethodFilter

我们已经讨论过重写 HTTP 方法。Spring中文文档

在 Spring WebFlux 应用程序中,使用 HiddenHttpMethodFilter 重写 HTTP 方法。Spring中文文档

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