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 使用 WebSessionServerCsrfTokenRepository 将预期的 CSRF 令牌存储在 WebSession 中。有时,您可能需要配置自定义的 ServerCsrfTokenRepository。例如,您可能希望将 CsrfToken 持久化到 cookie 中以 支持基于 JavaScript 的应用程序

默认情况下,CookieServerCsrfTokenRepository 写入名为 XSRF-TOKEN 的 cookie,并从名为 X-XSRF-TOKEN 的标头或 HTTP _csrf 参数中读取。这些默认值来自 AngularJS

您可以在 Java 配置中配置 CookieServerCsrfTokenRepository

将 CSRF 令牌存储在 Cookie 中
  • 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()
        }
    }
}

前面的示例明确设置了 cookieHttpOnly=false。这是为了让 JavaScript(在本例中为 AngularJS)能够读取它。如果您不需要直接通过 JavaScript 读取 cookie,我们建议省略 cookieHttpOnly=false(通过使用 new CookieServerCsrfTokenRepository() 代替)以提高安全性。

禁用 CSRF 保护

默认情况下,CSRF 保护是启用的。但是,如果 您的应用程序有合理的需求,您可以禁用 CSRF 保护。

以下 Java 配置将禁用 CSRF 保护。

禁用 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 借助 ServerCsrfTokenRequestHandlerMono<CsrfToken> 作为 ServerWebExchange 属性(名为 org.springframework.security.web.server.csrf.CsrfToken)公开。在 5.8 中,默认实现是 ServerCsrfTokenRequestAttributeHandler,它只是将 Mono<CsrfToken> 作为交换属性提供。

从 6.0 开始,默认实现是 XorServerCsrfTokenRequestAttributeHandler,它提供 BREACH 保护(参见 gh-4001)。

如果您希望禁用 CsrfToken 的 BREACH 保护并恢复到 5.8 默认值,您可以使用以下 Java 配置来配置 ServerCsrfTokenRequestAttributeHandler

禁用 BREACH 保护
  • 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 令牌

为了使 同步器令牌模式 能够防止 CSRF 攻击,我们必须在 HTTP 请求中包含实际的 CSRF 令牌。它必须包含在请求的一部分(表单参数、HTTP 标头或其他选项)中,而这部分不会由浏览器自动包含在 HTTP 请求中。

我们已经看到 Mono<CsrfToken> 作为 ServerWebExchange 属性公开。这意味着任何视图技术都可以访问 Mono<CsrfToken>,从而将预期令牌作为 表单元标记 公开。

如果您的视图技术不提供订阅 Mono<CsrfToken> 的简单方法,常见的模式是使用 Spring 的 @ControllerAdvice 直接公开 CsrfToken。以下示例将 CsrfToken 放在 Spring Security CsrfRequestDataValueProcessor 使用的默认属性名称 (_csrf) 上,以自动将 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) -> 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 令牌必须作为隐藏输入包含在表单中。以下示例显示了渲染的 HTML 可能是什么样子

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

接下来,我们讨论将 CSRF 令牌作为隐藏输入包含在表单中的各种方法。

自动包含 CSRF 令牌

Spring Security 的 CSRF 支持通过其 CsrfRequestDataValueProcessor 提供了与 Spring 的 RequestDataValueProcessor 的集成。为了使 CsrfRequestDataValueProcessor 工作,必须订阅 Mono<CsrfToken> 并且 CsrfToken 必须 作为属性公开,该属性与 CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME 匹配。

幸运的是,Thymeleaf 为您处理了所有样板文件,通过与 RequestDataValueProcessor 集成,确保使用不安全 HTTP 方法 (POST) 的表单自动包含实际的 CSRF 令牌。

CsrfToken 请求属性

如果将实际 CSRF 令牌包含在请求中的 其他选项 不起作用,您可以利用 Mono<CsrfToken> 作为 名为 org.springframework.security.web.server.csrf.CsrfTokenServerWebExchange 属性的事实。

以下 Thymeleaf 示例假设您将 CsrfToken 公开 为名为 _csrf 的属性

使用请求属性的表单中的 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 中,JavaScript 框架(例如 AngularJS)会自动将实际的 CSRF 令牌包含在 HTTP 请求头中。

元标记

将 CSRF 公开在 cookie 中 的另一种模式是将 CSRF 令牌包含在您的 meta 标记中。HTML 可能看起来像这样

CSRF 元标记 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,可以使用以下代码读取元标记

AJAX 发送 CSRF 令牌
$(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 完成此操作

CSRF 元标记 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 考虑事项

登录

您应该 要求登录请求进行 CSRF 保护,以防止伪造登录尝试。Spring Security 的 WebFlux 支持会自动执行此操作。

注销

您应该 要求注销请求进行 CSRF 保护,以防止伪造注销尝试。默认情况下,Spring Security 的 LogoutWebFilter 仅处理 HTTP POST 请求。这确保了注销需要 CSRF 令牌,并且恶意用户无法强制注销您的用户。

最简单的方法是使用表单注销。如果您确实想要一个链接,可以使用 JavaScript 让链接执行 POST 操作(可能在一个隐藏表单上)。对于禁用 JavaScript 的浏览器,您可以选择让链接将用户带到注销确认页面,该页面执行 POST 操作。

如果您确实想使用 HTTP GET 进行注销,也可以这样做,但请记住,通常不建议这样做。例如,以下 Java 配置在请求 /logout URL 时(使用任何 HTTP 方法)注销

使用 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 令牌存储在 WebSession 中。这种安排可能导致会话过期的情况,这意味着没有预期的 CSRF 令牌可供验证。

我们已经讨论了 解决会话超时的一般方案。本节讨论与 WebFlux 支持相关的 CSRF 超时的具体细节。

您可以将会话 CSRF 令牌的存储方式更改为 cookie。有关详细信息,请参阅 自定义 CsrfTokenRepository 部分。

多部分 (文件上传)

我们 已经讨论过 如何保护多部分请求(文件上传)免受 CSRF 攻击会导致“鸡生蛋还是蛋生鸡”的问题。本节讨论如何在 WebFlux 应用程序中实现将 CSRF 令牌放置在 请求体URL 中。

有关在 Spring 中使用多部分表单的更多信息,请参阅 Spring 参考文档的 多部分数据 部分。

将 CSRF 令牌放置在请求体中

我们 已经讨论过 将 CSRF 令牌放置在请求体中的权衡。

在 WebFlux 应用程序中,您可以使用以下配置来实现

启用从 multipart/form-data 获取 CSRF 令牌
  • 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 令牌

我们 已经讨论过 将 CSRF 令牌放置在 URL 中的权衡。由于 CsrfToken 作为 ServerHttpRequest 请求属性 公开,我们可以使用它来创建带有 CSRF 令牌的 action。下面是使用 Thymeleaf 的示例

Action 中的 CSRF 令牌
<form method="post"
	th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
	enctype="multipart/form-data">

HiddenHttpMethodFilter

我们 已经讨论过 覆盖 HTTP 方法。

在 Spring WebFlux 应用程序中,覆盖 HTTP 方法是通过使用 HiddenHttpMethodFilter 完成的。

© . This site is unofficial and not affiliated with VMware.