此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

跨站请求伪造(CSRF)环境

此部分讨论了Spring Security在WebFlux环境中的跨站请求伪造(CSRF)支持。spring-doc.cadn.net.cn

使用 Spring Security CSRF 保护

使用Spring Security的CSRF保护的步骤如下所示:spring-doc.cadn.net.cn

使用合适的HTTP动词

防止CSRF攻击的第一步是确保您的网站使用适当的HTTP动词。 这在安全方法必须只读中详细说明。spring-doc.cadn.net.cn

配置 CSRF 保护

接下来的步骤是配置Spring Security的CSRF保护功能。 默认情况下,Spring Security的CSRF保护已启用,但您可能需要自定义配置。 接下来的几个子部分将涵盖一些常见的自定义配置。spring-doc.cadn.net.cn

自定义 CsrfTokenRepository

默认情况下,Spring Security 将预期的 CSRF Tokens存储在 WebSession 中,使用的是 WebSessionServerCsrfTokenRepository。 有时,您可能需要配置一个自定义的 ServerCsrfTokenRepository。例如,您可能希望将 CsrfToken 存储在 cookie 中以 支持基于 JavaScript 的应用spring-doc.cadn.net.cn

默认情况下,CookieServerCsrfTokenRepository 将数据写入名为 XSRF-TOKEN 的 cookie,并从名为 X-XSRF-TOKEN 或 HTTP _csrf 参数的头部读取。 这些默认设置来自 AngularJSspring-doc.cadn.net.cn

您可以使用Java配置来配置CookieServerCsrfTokenRepositoryspring-doc.cadn.net.cn

存储 CSRF Tokens到一个 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()
        }
    }
}

在前面的示例中,cookieHttpOnly=false 显式设置了此属性。 这是为了让 JavaScript(在这种情况下为 AngularJS)能够读取该 cookie 所必需的。 如果不需要直接通过 JavaScript 读取 cookie 的能力,则我们建议省略 cookieHttpOnly=false(可以通过使用 new CookieServerCsrfTokenRepository() 来实现),以提高安全性。spring-doc.cadn.net.cn

禁用 CSRF 保护

默认情况下,CSRF保护是启用的。 但是,如果对您的应用程序来说禁用CSRF保护是有道理的,您可以选择禁用它。spring-doc.cadn.net.cn

以下的Java配置将禁用CSRF保护。spring-doc.cadn.net.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 的 CsrfWebFilter 借助 ServerCsrfTokenRequestHandler,将 Mono<CsrfToken> 作为名为 org.springframework.security.web.server.csrf.CsrfTokenServerWebExchange 属性公开。 在 5.8 版本中,默认实现是 ServerCsrfTokenRequestAttributeHandler,它仅使 Mono<CsrfToken> 可作为交换属性使用。spring-doc.cadn.net.cn

自 6.0 版本起,默认实现为 XorServerCsrfTokenRequestAttributeHandler,它提供了对 BREACH 的保护(参见 gh-4001)。spring-doc.cadn.net.cn

如果您希望禁用CsrfToken的BREACH保护并恢复到5.8版本的默认设置,可以使用以下Java配置来配置ServerCsrfTokenRequestAttributeHandler:spring-doc.cadn.net.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()
        }
    }
}

包含CSRFTokens

对于同步Tokens模式来保护免受 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF Tokens。 它必须包含在 HTTP 请求的一部分(表单参数、HTTP 头部或其他选项)中,而浏览器不会自动将这部分内容包含在 HTTP 请求中。spring-doc.cadn.net.cn

我们已经看到Mono<CsrfToken>作为ServerWebExchange的一个属性被暴露出来。 这意味着任何视图技术都可以访问Mono<CsrfToken>以将其期望的Tokens作为表单中的表单字段或元标签中的meta标签进行呈现。spring-doc.cadn.net.cn

如果您的视图技术没有提供订阅Mono<CsrfToken>的简单方法,一个常见的模式是使用Spring的@ControllerAdvice来直接暴露CsrfToken。 以下示例将CsrfToken放置在由Spring Security的CsrfRequestDataValueProcessor使用的默认属性名称(_csrf)上,从而自动将CSRFTokens作为隐藏输入包含进来:spring-doc.cadn.net.cn

CsrfToken as @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.cadn.net.cn

表单 URL 编码

要提交一个 HTML 表单,必须在表单中包含一个隐藏的输入来包含 CSRF Tokens。 以下示例展示了渲染后的 HTML 可能的样子:spring-doc.cadn.net.cn

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

接下来,我们讨论在表单中以隐藏输入方式包含CSRFTokens的各种方法。spring-doc.cadn.net.cn

自动包含 CSRF Tokens

Spring Security 的 CSRF 支持通过其 CsrfRequestDataValueProcessor 与 Spring 的 RequestDataValueProcessor 集成。 为了使 CsrfRequestDataValueProcessor 正常工作,必须订阅 Mono<CsrfToken>,并且 CsrfToken 必须作为属性 公开,且该属性需匹配 CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAMEspring-doc.cadn.net.cn

幸运的是,Thymeleaf 为你处理了所有模板的样板代码,通过与RequestDataValueProcessor集成,确保使用不安全HTTP方法(POST)提交的表单会自动包含实际的CSRFTokens。spring-doc.cadn.net.cn

CsrfToken 请求属性

如果其他选项无法将实际的 CSRF Tokens包含在请求中,您可以利用 #webflux-csrf-include 作为 Mono<CsrfToken> 属性 #webflux-csrf-include 被公开的事实。spring-doc.cadn.net.cn

The following Thymeleaf sample assumes that you 暴露 CsrfToken 在一个名为 _csrf 的属性上:spring-doc.cadn.net.cn

CSRF Token 在表单中的请求属性
<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参数中提交CSRFTokens。 相反,您可以将Tokens提交到HTTP标头中。spring-doc.cadn.net.cn

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

自动包含

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

元标签

一种替代的模式是将 CSRF 在 meta 标签中包含进去,而不是通过 cookie 暴露 CSRF。例如,HTML 可能看起来像这样:spring-doc.cadn.net.cn

CSRF meta标签HTML
<html>
<head>
	<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
	<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
	<!-- ... -->
</head>
<!-- ... -->

一旦元标签包含 CSRF Tokens,JavaScript 代码就可以读取元标签并将 CSRF Tokens作为头信息包含进去。 如果使用 jQuery,可以通过以下代码读取元标签:spring-doc.cadn.net.cn

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

The following sample assumes that you 暴露CsrfToken 在名为 _csrf 的属性上。 The following example does this with Thymeleaf:spring-doc.cadn.net.cn

CSRF meta标签 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>
<!-- ... -->

CSRF 注意事项

在实现防止 CSRF 攻击时,有几项特殊考虑需要考虑。 本节讨论这些考虑事项,特别是在 WebFlux 环境中的应用。 如需更一般的讨论,请参阅 CSRF 考虑事项spring-doc.cadn.net.cn

登录

您应该为登录请求要求 CSRF 保护以防止伪造的登录尝试。 Spring Security 的 WebFlux 支持会自动实现这一点。spring-doc.cadn.net.cn

正在退出

您应该为注销请求启用CSRF保护以防止伪造注销尝试。 默认情况下,Spring Security的LogoutWebFilter仅处理HTTP POST请求。 这确保了注销需要CSRFTokens,并且恶意用户无法强制使您的用户退出登录。spring-doc.cadn.net.cn

使用表单进行登出是最简单的方法。 如果你真的需要一个链接,你可以使用JavaScript使链接执行POST操作(可能在一个隐藏的表单中)。 对于禁用了JavaScript的浏览器,你还可以选择让链接跳转到一个确认页面,在该页面上执行POST操作。spring-doc.cadn.net.cn

如果确实要使用HTTP GET进行注销,请这样做,但请记住这通常不被推荐。 例如,以下Java配置会在请求/logout URL时进行注销,并且不限于HTTP GET方法:spring-doc.cadn.net.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 Tokens存储在 WebSession 中。 这种安排可能导致会话过期的情况,这意味着没有预期的 CSRF Tokens可以进行验证。spring-doc.cadn.net.cn

我们已经讨论过一般解决方案来处理会话超时。 本节将讨论与WebFlux支持相关的CSRF超时的具体情况。spring-doc.cadn.net.cn

您可以通过更改预期的CSRFTokens存储位置,使其存储在cookie中。 有关详细信息,请参阅自定义CsrfTokenRepository部分。spring-doc.cadn.net.cn

多部分(文件上传)

我们已经在之前讨论过如何保护multipart请求(文件上传)免受CSRF攻击会导致一个鸡和蛋问题。 本节将讨论在WebFlux应用中如何实现将CSRFTokens放置在请求体URL中的方法。spring-doc.cadn.net.cn

关于使用multipart表单与Spring的相关信息,请参阅Spring参考文档中的Multipart数据部分。spring-doc.cadn.net.cn

将 CSRF Tokens放入请求体中

我们已经讨论过将 CSRF Tokens放在请求体中的权衡问题。spring-doc.cadn.net.cn

在WebFlux 应用程序中,您可以使用以下配置实现:spring-doc.cadn.net.cn

从multipart/form-data中启用获取CSRFTokens
@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 Tokens

我们已经在文档中讨论过将CSRFTokens放在URL中的权衡。 由于CsrfToken作为ServerHttpRequest的一个请求属性被暴露,我们可以利用这一点来创建一个包含CSRFTokens的action。 以下是一个使用Thymeleaf的例子:spring-doc.cadn.net.cn

CSRF Tokens的实际应用
<form method="post"
	th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
	enctype="multipart/form-data">

隐藏 HTTP 方法过滤器

我们已经讨论过覆盖HTTP方法的问题。spring-doc.cadn.net.cn

在 Spring WebFlux 应用中,通过 HiddenHttpMethodFilter 来覆盖 HTTP 方法。spring-doc.cadn.net.cn