对于最新的稳定版本,请使用 Spring Security 6.5.3! |
WebFlux 环境的跨站点请求伪造 (CSRF)
本节讨论 Spring Security 对 WebFlux 环境的跨站点请求伪造 (CSRF) 支持。
使用 Spring Security CSRF 保护
下面概述了使用 Spring Security 的 CSRF 保护的步骤:
使用正确的 HTTP 动词
防范 CSRF 攻击的第一步是确保您的网站使用正确的 HTTP 动词。 安全方法必须幂等中详细介绍了这一点。
配置 CSRF 保护
下一步是在应用程序中配置 Spring Security 的 CSRF 保护。 默认情况下,Spring Security 的 CSRF 保护处于启用状态,但您可能需要自定义配置。 接下来的几个小节介绍了一些常见的自定义。
自定义 CsrfTokenRepository
默认情况下,Spring Security 将预期的 CSRF Tokens存储在WebSession
通过使用WebSessionServerCsrfTokenRepository
.
有时,您可能需要配置自定义ServerCsrfTokenRepository
.
例如,您可能希望保留CsrfToken
在 cookie 中支持基于 JavaScript 的应用程序。
默认情况下,CookieServerCsrfTokenRepository
写入名为XSRF-TOKEN
并从名为X-XSRF-TOKEN
或 HTTP_csrf
参数。
这些默认值来自 AngularJS
您可以配置CookieServerCsrfTokenRepository
在 Java 配置中:
-
Java
-
Kotlin
@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()
}
}
}
前面的示例显式设置 |
禁用 CSRF 保护
默认情况下,CSRF 保护处于启用状态。 但是,如果 CSRF 保护对您的应用程序有意义,则可以禁用 CSRF 保护。
下面的 Java 配置将禁用 CSRF 保护。
-
Java
-
Kotlin
@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
公开一个Mono<CsrfToken>
作为ServerWebExchange
名为org.springframework.security.web.server.csrf.CsrfToken
在ServerCsrfTokenRequestHandler
.
在 5.8 中,默认实现是ServerCsrfTokenRequestAttributeHandler
,这只是使Mono<CsrfToken>
可作为 Exchange 属性使用。
从 6.0 开始,默认实现为XorServerCsrfTokenRequestAttributeHandler
,为 BREACH 提供保护(参见 gh-4001)。
如果您希望禁用CsrfToken
并恢复为 5.8 默认值,您可以配置ServerCsrfTokenRequestAttributeHandler
使用以下 Java 配置:
-
Java
-
Kotlin
@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 Tokens
为了使同步器Tokens模式防范 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF Tokens。 它必须包含在浏览器不会自动包含在 HTTP 请求中的请求的一部分(表单参数、HTTP 标头或其他选项)中。
如果您的视图技术没有提供一种简单的方法来订阅Mono<CsrfToken>
,一个常见的模式是使用 Spring 的@ControllerAdvice
以公开CsrfToken
径直。
以下示例将CsrfToken
在默认属性名称 (_csrf
) 用于自动将 CSRF Tokens作为隐藏输入包含在内:
CsrfToken
如@ModelAttribute
-
Java
-
Kotlin
@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 提供了无需任何额外工作的集成。
表单 URL 编码
要发布 HTML 表单,必须将 CSRF Tokens作为隐藏输入包含在表单中。 以下示例显示了呈现的 HTML 可能是什么样子:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
接下来,我们讨论将 CSRF Tokens包含在表单中作为隐藏输入的各种方法。
自动包含 CSRF Tokens
Spring Security 的 CSRF 支持提供了与 Spring 的RequestDataValueProcessor
通过其CsrfRequestDataValueProcessor
.
为CsrfRequestDataValueProcessor
工作,Mono<CsrfToken>
必须订阅和CsrfToken
必须公开为匹配的属性DEFAULT_CSRF_ATTR_NAME
.
幸运的是,Thymeleaf 通过集成为您处理所有样板RequestDataValueProcessor
以确保具有不安全 HTTP 方法 (POST) 的表单自动包含实际的 CSRF Tokens。
CsrfToken 请求属性
如果在请求中包含实际 CSRF Tokens的其他选项不起作用,您可以利用以下事实:Mono<CsrfToken>
被公开为ServerWebExchange
名为org.springframework.security.web.server.csrf.CsrfToken
.
以下 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 Tokens。 相反,您可以在 HTTP 标头中提交Tokens。
在以下部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF Tokens作为 HTTP 请求标头包含的各种方法。
元标记
在 cookie 中公开 CSRF 的另一种模式是在meta
标签。 HTML 可能如下所示:
<html>
<head>
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
<!-- ... -->
</head>
<!-- ... -->
一旦元标记包含 CSRF 标记,JavaScript 代码就可以读取元标记并将 CSRF 标记作为标头包含在内。如果您使用 jQuery,则可以使用以下代码读取元标记:
$(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 Tokens,并且恶意用户无法强制注销您的用户。
最简单的方法是使用表单注销。 如果你真的想要一个链接,你可以使用 JavaScript 让链接执行 POST(可能在隐藏表单上)。 对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到执行 POST 的注销确认页面。
如果您真的想在注销时使用 HTTP GET,您可以这样做,但请记住,通常不建议这样做。
例如,以下 Java 配置在/logout
使用任何 HTTP 方法请求 URL:
-
Java
-
Kotlin
@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可以验证。
我们已经讨论了会话超时的一般解决方案。 本节讨论与 WebFlux 支持相关的 CSRF 超时的细节。
您可以将预期的 CSRF Tokens的存储更改为 Cookie 中。有关详细信息,请参阅自定义 CsrfTokenRepository 部分。
分段(文件上传)
有关将多部分表单与 Spring 结合使用的更多信息,请参阅 Spring 参考的多部分数据部分。 |
将 CSRF Tokens放入正文中
我们已经讨论了将 CSRF Tokens放入正文中的权衡。
在 WebFlux 应用程序中,您可以使用以下配置来执行此作:
-
Java
-
Kotlin
@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
我们已经讨论了将 CSRF Tokens放置在 URL 中的权衡。
由于CsrfToken
被公开为ServerHttpRequest
request 属性,我们可以使用它来创建一个action
其中包含 CSRF Tokens。
胸腺叶的一个例子如下所示:
<form method="post"
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
enctype="multipart/form-data">