|
此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 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">