|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4! |
跨站请求伪造(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 的 cookie,并从名为 X-XSRF-TOKEN 或 HTTP _csrf 参数的头部读取。
这些默认设置来自 AngularJS。
您可以使用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()
}
}
}
|
在前面的示例中, |
禁用 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 借助 ServerCsrfTokenRequestHandler,将 Mono<CsrfToken> 作为名为 org.springframework.security.web.server.csrf.CsrfToken 的 ServerWebExchange 属性公开。
在 5.8 版本中,默认实现是 ServerCsrfTokenRequestAttributeHandler,它仅使 Mono<CsrfToken> 可作为交换属性使用。
自 6.0 版本起,默认实现为 XorServerCsrfTokenRequestAttributeHandler,它提供了对 BREACH 的保护(参见 gh-4001)。
如果您希望禁用CsrfToken的BREACH保护并恢复到5.8版本的默认设置,可以使用以下Java配置来配置ServerCsrfTokenRequestAttributeHandler:
-
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()
}
}
}
包含CSRFTokens
对于同步Tokens模式来保护免受 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF Tokens。 它必须包含在 HTTP 请求的一部分(表单参数、HTTP 头部或其他选项)中,而浏览器不会自动将这部分内容包含在 HTTP 请求中。
我们已经看到,Mono<CsrfToken>作为ServerWebExchange的一个属性被暴露出来。
这意味着任何视图技术都可以访问Mono<CsrfToken>以将其期望的Tokens作为表单中的表单字段或元标签中的meta标签进行呈现。
如果您的视图技术没有提供订阅Mono<CsrfToken>的简单方法,一个常见的模式是使用Spring的@ControllerAdvice来直接暴露CsrfToken。
以下示例将CsrfToken放置在由Spring Security的CsrfRequestDataValueProcessor使用的默认属性名称(_csrf)上,从而自动将CSRFTokens作为隐藏输入包含进来:
CsrfToken as @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) -> token.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"/>
接下来,我们讨论在表单中以隐藏输入方式包含CSRFTokens的各种方法。
自动包含 CSRF Tokens
Spring Security 的 CSRF 支持通过其 CsrfRequestDataValueProcessor 与 Spring 的 RequestDataValueProcessor 集成。
为了使 CsrfRequestDataValueProcessor 正常工作,必须订阅 Mono<CsrfToken>,并且 CsrfToken 必须作为属性 公开,且该属性需匹配 CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME。
幸运的是,Thymeleaf 为你处理了所有模板的样板代码,通过与RequestDataValueProcessor集成,确保使用不安全HTTP方法(POST)提交的表单会自动包含实际的CSRFTokens。
CsrfToken 请求属性
如果其他选项无法将实际的 CSRF Tokens包含在请求中,您可以利用 #webflux-csrf-include 作为 Mono<CsrfToken> 属性 #webflux-csrf-include 被公开的事实。
The following Thymeleaf sample assumes that you 暴露 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参数中提交CSRFTokens。 相反,您可以将Tokens提交到HTTP标头中。
在以下部分,我们将讨论各种将CSRFTokens作为HTTP请求头包含在基于JavaScript的应用程序中的方法。
元标签
一种替代的模式是将 CSRF 在 meta 标签中包含进去,而不是通过 cookie 暴露 CSRF。例如,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,可以通过以下代码读取元标签:
$(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:
<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请求。
这确保了注销需要CSRFTokens,并且恶意用户无法强制使您的用户退出登录。
使用表单进行登出是最简单的方法。 如果你真的需要一个链接,你可以使用JavaScript使链接执行POST操作(可能在一个隐藏的表单中)。 对于禁用了JavaScript的浏览器,你还可以选择让链接跳转到一个确认页面,在该页面上执行POST操作。
如果确实要使用HTTP GET进行注销,请这样做,但请记住这通常不被推荐。
例如,以下Java配置会在请求/logout URL时进行注销,并且不限于HTTP GET方法:
-
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超时的具体情况。
您可以通过更改预期的CSRFTokens存储位置,使其存储在cookie中。 有关详细信息,请参阅自定义CsrfTokenRepository部分。
多部分(文件上传)
我们已经讨论过,保护多部分请求(文件上传)免受 CSRF 攻击会引发一个鸡生蛋、蛋生鸡的问题。 当 JavaScript 可用时,我们建议在 HTTP 请求头中包含 CSRF Tokens(参见AJAX 和 JSON 请求),以绕过该问题。 如果 JavaScript 不可用,本节将讨论如何在 WebFlux 应用程序中将 CSRF Tokens放置在请求体和URL中。
|
关于使用multipart表单与Spring的相关信息,请参阅Spring参考文档中的Multipart数据部分。 |
将 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
}
}
}