此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1! |
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 令牌存储在 using .
在某些情况下,用户可能希望配置自定义 .
例如,可能需要将 持久保存在 Cookie 中以支持基于 JavaScript 的应用程序。WebSession
WebSessionServerCsrfTokenRepository
ServerCsrfTokenRepository
CsrfToken
默认情况下,将写入名为 的 Cookie,并从名为 或 HTTP 参数的标头中读取它。
这些默认值来自 AngularJSCookieServerCsrfTokenRepository
XSRF-TOKEN
X-XSRF-TOKEN
_csrf
您可以在 Java 配置中使用以下方式进行配置:CookieServerCsrfTokenRepository
-
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()
}
}
}
该示例显式设置 .
这是允许 JavaScript(即 AngularJS)读取它所必需的。
如果您不需要直接使用 JavaScript 读取 cookie 的能力,建议省略 (改用) 以提高安全性。 |
禁用 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>
公开为在ServerCsrfTokenRequestHandler
的帮助下命名的属性。
默认实现为 .ServerWebExchange
org.springframework.security.web.server.csrf.CsrfToken
ServerCsrfTokenRequestAttributeHandler
可以使用替代实现来为 BREACH 提供保护(请参阅 gh-4001)。XorServerCsrfTokenRequestAttributeHandler
您可以使用以下 Java 配置进行配置:XorServerCsrfTokenRequestAttributeHandler
-
Java
-
Kotlin
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.csrf(csrf -> csrf
.csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler())
)
return http.build();
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
csrf {
csrfTokenRequestHandler = XorServerCsrfTokenRequestAttributeHandler()
}
}
}
包括 CSRF 令牌
为了使同步器令牌模式能够防止 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。 这必须包含在浏览器不会自动包含在 HTTP 请求中的请求部分(即表单参数、HTTP 标头等)中。
我们已经看到 the 被公开为一个属性。
这意味着任何视图技术都可以访问 以将预期的令牌公开为 form 或 meta 标记。Mono<CsrfToken>
ServerWebExchange
Mono<CsrfToken>
如果你的视图技术没有提供一种简单的方法来订阅 ,一种常见的模式是使用 Spring 的直接暴露。
例如,下面的代码将把 Spring Security 的 CsrfRequestDataValueProcessor 使用的默认属性名称 () 放在上面,以自动将 CSRF 令牌作为隐藏输入包含。Mono<CsrfToken>
@ControllerAdvice
CsrfToken
CsrfToken
_csrf
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 令牌必须作为隐藏输入包含在表单中。 例如,呈现的 HTML 可能如下所示:
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
接下来,我们将讨论将 CSRF 令牌作为隐藏输入包含在表单中的各种方法。
自动包含 CSRF 令牌
Spring Security 的 CSRF 支持通过其CsrfRequestDataValueProcessor提供与 Spring 的RequestDataValueProcessor的集成。
为了正常工作,必须订阅 the 并且 必须作为与 DEFAULT_CSRF_ATTR_NAME 匹配的属性公开。CsrfRequestDataValueProcessor
Mono<CsrfToken>
CsrfToken
幸运的是,Thymeleaf 通过与 集成来提供支持,以确保具有不安全 HTTP 方法(即 post)的表单将自动包含实际的 CSRF 令牌。RequestDataValueProcessor
CsrfToken 请求属性
如果在请求中包含实际 CSRF 令牌的其他选项不起作用,您可以利用 作为名为 的属性公开的事实。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 令牌。 相反,您可以在 HTTP 标头中提交令牌。
在以下部分中,我们将讨论在基于 JavaScript 的应用程序中将 CSRF 令牌作为 HTTP 请求标头包含的各种方法。
自动收录
Spring Security 可以很容易地配置为将预期的 CSRF 令牌存储在 cookie 中。 通过将预期的 CSRF 存储在 cookie 中,像 AngularJS 这样的 JavaScript 框架会自动在 HTTP 请求标头中包含实际的 CSRF 令牌。
元标记
在 cookie 中公开 CSRF 的另一种模式是将 CSRF 令牌包含在您的标签中。
HTML 可能如下所示:meta
<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,则可以通过以下方式完成此操作:
$(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);
});
});
以下示例假定您在名为 .
使用 Lymeleaf 执行此操作的示例如下所示:CsrfToken
_csrf
<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 仅处理 HTTP POST 请求。
这样可以确保注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。LogoutWebFilter
最简单的方法是使用表单注销。 如果你真的想要一个链接,你可以使用 JavaScript 让链接执行 POST(即可能在隐藏的表单上)。 对于禁用了 JavaScript 的浏览器,您可以选择让链接将用户带到将执行 POST 的注销确认页面。
如果您确实想将 HTTP GET 与 logout 一起使用,您可以这样做,但请记住,通常不建议这样做。
例如,以下 Java 配置将使用任何 HTTP 方法请求 URL 来执行注销:/logout
-
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 令牌存储在 .
这可能会导致会话过期的情况,这意味着没有预期的 CSRF 令牌进行验证。WebSession
我们已经讨论了会话超时的一般解决方案。 本节讨论与 WebFlux 支持相关的 CSRF 超时的细节。
将预期的 CSRF 令牌的存储更改为 cookie 很简单。 有关详细信息,请参阅 自定义 CsrfTokenRepository 部分。
分段(文件上传)
有关在 Spring 中使用多部分形式的更多信息,可以在 Spring 参考的 Multipart Data 部分中找到。 |
将 CSRF 令牌放在正文中
我们已经讨论了将 CSRF 令牌放入 body 中的权衡。
在 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 令牌
我们已经讨论了在 URL 中放置 CSRF 令牌的权衡。
由于 the 是作为 request 属性公开的,我们可以使用它来创建一个带有 CSRF 令牌的 s。
Thymeleaf 的示例如下所示:CsrfToken
ServerHttpRequest
action
<form method="post"
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
enctype="multipart/form-data">
隐藏的HttpMethodFilter
我们已经讨论了重写 HTTP 方法。
在 Spring WebFlux 应用程序中,覆盖 HTTP 方法是使用 HiddenHttpMethodFilter 完成的。