对于最新的稳定版本,请使用 Spring Security 6.3.3spring-doc.cn

对于最新的稳定版本,请使用 Spring Security 6.3.3spring-doc.cn

本节讨论 Spring Security 对 WebFlux 环境的跨站点请求伪造 (CSRF) 支持。spring-doc.cn

使用 Spring Security CSRF 保护

使用 Spring Security 的 CSRF 保护的步骤概述如下:spring-doc.cn

使用正确的 HTTP 动词

防止 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 动词。 安全方法必须为只读中对此进行了详细介绍。spring-doc.cn

配置 CSRF 保护

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

自定义 CsrfTokenRepository

默认情况下,Spring Security 使用 将预期的 CSRF 令牌存储在 中。 有时,您可能需要配置自定义 . 例如,您可能希望将 持久保存在 Cookie 中以支持基于 JavaScript 的应用程序WebSessionWebSessionServerCsrfTokenRepositoryServerCsrfTokenRepositoryCsrfTokenspring-doc.cn

默认情况下,写入名为 的 Cookie,并从名为 HTTP 参数的标头读取该 Cookie。 这些默认值来自 AngularJSCookieServerCsrfTokenRepositoryXSRF-TOKENX-XSRF-TOKEN_csrfspring-doc.cn

您可以在 Java 配置中配置:CookieServerCsrfTokenRepositoryspring-doc.cn

将 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-doc.cn

禁用 CSRF 保护

默认情况下,CSRF 保护处于启用状态。 但是,如果 CSRF 保护对您的应用程序有意义,您可以禁用它。spring-doc.cn

下面的 Java 配置将禁用 CSRF 保护。spring-doc.cn

禁用 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 中,默认实现是 ,它只是使 作为 exchange 属性可用。ServerWebExchangeorg.springframework.security.web.server.csrf.CsrfTokenServerCsrfTokenRequestAttributeHandlerMono<CsrfToken>spring-doc.cn

从 6.0 开始,默认实现为 ,它为 BREACH 提供保护(请参阅 gh-4001)。XorServerCsrfTokenRequestAttributeHandlerspring-doc.cn

如果您希望禁用 的 BREACH 保护并恢复到 5.8 默认值,您可以使用以下 Java 配置进行配置:CsrfTokenServerCsrfTokenRequestAttributeHandlerspring-doc.cn

禁用 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-doc.cn

我们已经看到 the 被公开为一个属性。 这意味着任何视图技术都可以访问 ,以将预期的令牌公开为 formmeta 标记Mono<CsrfToken>ServerWebExchangeMono<CsrfToken>spring-doc.cn

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

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-doc.cn

表单 URL 编码

要发布 HTML 表单,CSRF 令牌必须作为隐藏输入包含在表单中。 以下示例显示了呈现的 HTML 可能是什么样子:spring-doc.cn

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

接下来,我们讨论将 CSRF 令牌作为隐藏输入包含在表单中的各种方法。spring-doc.cn

自动包含 CSRF 令牌

Spring Security 的 CSRF 支持通过其CsrfRequestDataValueProcessor提供与 Spring 的RequestDataValueProcessor的集成。 要正常工作,必须订阅 the 并且必须作为与 DEFAULT_CSRF_ATTR_NAME 匹配的属性公开CsrfRequestDataValueProcessorMono<CsrfToken>CsrfTokenspring-doc.cn

幸运的是,Thymeleaf 通过与 集成来确保具有不安全 HTTP 方法(POST)的表单自动包含实际的 CSRF 令牌,从而为您处理所有样板文件。RequestDataValueProcessorspring-doc.cn

CsrfToken 请求属性

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

以下 Thymeleaf 示例假定您公开名为 :CsrfToken_csrfspring-doc.cn

表单中具有 request 属性的 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-doc.cn

在以下部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求标头包含的各种方法。spring-doc.cn

自动收录

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

元标记

在 cookie 中公开 CSRF 的另一种模式是将 CSRF 令牌包含在您的标签中。 HTML 可能如下所示:metaspring-doc.cn

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-doc.cn

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

以下示例假定您公开了名为 . 下面的示例使用 Thymeleaf 执行此操作:CsrfToken_csrfspring-doc.cn

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-doc.cn

CSRF 注意事项

在实施针对 CSRF 攻击的保护时,需要考虑一些特殊的注意事项。 本节讨论与 WebFlux 环境相关的这些注意事项。 有关更一般的讨论,请参阅 CSRF 注意事项spring-doc.cn

登录

您应该要求对登录请求使用 CSRF,以防止伪造的登录尝试。 Spring Security 的 WebFlux 支持会自动执行此操作。spring-doc.cn

注销

您应该要求对注销请求使用 CSRF,以防止伪造注销尝试。 默认情况下,Spring Security 仅处理 HTTP POST 请求。 这样可以确保注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。LogoutWebFilterspring-doc.cn

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

如果您确实希望将 HTTP GET 与 logout 一起使用,则可以这样做,但请记住,通常不建议这样做。 例如,当使用任何 HTTP 方法请求 URL 时,以下 Java 配置将注销:/logoutspring-doc.cn

使用 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-doc.cn

我们已经讨论了会话超时的一般解决方案。 本节讨论与 WebFlux 支持相关的 CSRF 超时的细节。spring-doc.cn

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

分段(文件上传)

我们已经讨论了保护分段请求(文件上传)免受 CSRF 攻击如何导致先有鸡还是先有蛋的问题。 本节讨论如何实现将 CSRF 令牌放置在 WebFlux 应用程序中的 bodyurl 中。spring-doc.cn

有关在 Spring 中使用多部分形式的更多信息,请参阅 Spring 参考的 Multipart Data 部分。spring-doc.cn

将 CSRF 令牌放在正文中

我们已经讨论了将 CSRF 令牌放入 body 中的权衡。spring-doc.cn

在 WebFlux 应用程序中,您可以使用以下配置来执行此操作:spring-doc.cn

启用从 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 令牌

我们已经讨论了在 URL 中放置 CSRF 令牌的权衡。 由于 the 是作为 request 属性公开的,我们可以使用它来创建一个带有 CSRF 令牌的 s。 Thymeleaf 的示例如下所示:CsrfTokenServerHttpRequestactionspring-doc.cn

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

隐藏的HttpMethodFilter

我们已经讨论了重写 HTTP 方法。spring-doc.cn

在 Spring WebFlux 应用程序中,通过使用HiddenHttpMethodFilter覆盖 HTTP 方法。spring-doc.cn

有关在 Spring 中使用多部分形式的更多信息,请参阅 Spring 参考的 Multipart Data 部分。spring-doc.cn