对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

以下步骤与有关如何配置 CSRF 的更改相关。Spring中文文档

延迟加载 CsrfToken

在 Spring Security 5 中,默认行为是将在每个请求上加载 。 这意味着在典型的设置中,即使没有必要,也必须为每个请求读取 。CsrfTokenHttpSessionSpring中文文档

一些不必要的读取会话的示例包括标记的端点,例如静态资产、静态 HTML 页面、托管在同一域/服务器下的单页应用程序等。permitAll()Spring中文文档

在 Spring Security 6 中,默认情况下,查找 将推迟到需要时。CsrfTokenSpring中文文档

每当使用会更改应用程序状态的 HTTP 谓词发出请求时,都需要 。 安全方法必须是只读中对此进行了详细介绍。 此外,任何将令牌呈现到响应的请求都需要它,例如具有包含隐藏的 CSRF 令牌的标记的网页。CsrfToken<form><input>Spring中文文档

要选择加入新的 Spring Security 6 默认值,可以使用以下配置。Spring中文文档

延迟加载CsrfToken
@Bean
public SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	http {
		csrf {
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
	p:csrfRequestAttributeName="_csrf"/>

当延迟时(Spring Security 6 中的默认值),某些应用程序可能会中断,因为它们是使用非延迟 CSRF 令牌设计的。 有关详细信息,请参阅下面的选择退出步骤CsrfTokenSpring中文文档

选择退出步骤

如果配置“要推迟”会给您带来麻烦,请查看以下方案,以获得最佳选择退出行为:CsrfTokenSpring中文文档

我正在使用单页应用程序CookieCsrfTokenRepository

如果您使用单页应用程序 (SPA) 连接到受 Spring Security 保护的后端,您可能会发现 CSRF 令牌不再在第一次向服务器请求时作为 cookie 返回到您的应用程序。CookieCsrfTokenRepository.withHttpOnlyFalse()Spring中文文档

在这种情况下,您可以使用多个选项来还原客户端应用程序期望的行为。 一种选择是添加一个,无论先发出哪个请求,它都会急切地呈现到响应中,如下所示:FilterCsrfTokenSpring中文文档

添加 a 以在响应上返回 cookieFilter
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		)
		.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);

	return http.build();
}

private static final class CsrfCookieFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.getToken();

		filterChain.doFilter(request, response);
	}

}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
	val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	http {
		csrf {
			csrfTokenRepository = tokenRepository
			csrfTokenRequestHandler = requestHandler
		}
		addFilterAfter<BasicAuthenticationFilter>(CsrfCookieFilter())
	}
	return http.build()
}

class CsrfCookieFilter : OncePerRequestFilter() {

	override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
		val csrfToken = request.getAttribute(CsrfToken::class.java.name) as CsrfToken
		// Render the token value to a cookie by causing the deferred token to be loaded
		csrfToken.token

		filterChain.doFilter(request, response)
	}

}

上面的选项不需要对单页应用程序进行更改,但会导致在每个请求上加载。 如果您不希望在每个请求上都添加一个以急切地加载令牌,下面列出了其他选项。CsrfTokenFilterSpring中文文档

我正在使用单页应用程序HttpSessionCsrfTokenRepository

如果使用会话,则应用程序将受益于延迟令牌。 另一种选择不是选择退出,而是添加带有终结点的新终结点,如下所示:@RestController/csrfSpring中文文档

添加终结点/csrf
@RestController
public class CsrfController {

    @GetMapping("/csrf")
    public CsrfToken csrf(CsrfToken csrfToken) {
        return csrfToken;
    }

}
@RestController
class CsrfController {

    @GetMapping("/csrf")
    fun csrf(csrfToken: CsrfToken): CsrfToken {
        return csrfToken
    }

}

在向服务器进行身份验证之前,如果需要上述终结点,则可以考虑添加。.requestMatchers("/csrf").permitAll()Spring中文文档

客户端应用程序需要使用终结点,以便为后续请求引导应用程序。/csrfSpring中文文档

有关在应用程序启动时调用终结点的说明特定于客户端框架,因此不在本文档的讨论范围之内。/csrfSpring中文文档

虽然这需要对单页应用程序进行更改,但好处是 CSRF 令牌仅加载一次,并且令牌可以继续延迟。 此方法特别适用于使用延迟令牌并从中受益的应用程序,因为它允许在每个请求上不读取令牌。HttpSessionCsrfTokenRepositoryHttpSessionSpring中文文档

如果您只是想完全退出延期令牌,则接下来列出了该选项。Spring中文文档

出于其他原因,我需要选择退出延期令牌

如果延迟令牌因其他原因中断了您的应用程序,则可以使用以下配置显式选择加入 5.8 默认值:Spring中文文档

显式配置 5.8 默认值CsrfToken
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName(null);
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = CsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName(null)
	http {
		csrf {
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
	<b:property name="csrfRequestAttributeName">
		<b:null/>
	</b:property>
</b:bean>

通过将 设置为 ,必须首先加载以确定要使用的属性名称。 这会导致在每个请求上加载 。csrfRequestAttributeNamenullCsrfTokenCsrfTokenSpring中文文档

一些不必要的读取会话的示例包括标记的端点,例如静态资产、静态 HTML 页面、托管在同一域/服务器下的单页应用程序等。permitAll()Spring中文文档

每当使用会更改应用程序状态的 HTTP 谓词发出请求时,都需要 。 安全方法必须是只读中对此进行了详细介绍。 此外,任何将令牌呈现到响应的请求都需要它,例如具有包含隐藏的 CSRF 令牌的标记的网页。CsrfToken<form><input>Spring中文文档

当延迟时(Spring Security 6 中的默认值),某些应用程序可能会中断,因为它们是使用非延迟 CSRF 令牌设计的。 有关详细信息,请参阅下面的选择退出步骤CsrfTokenSpring中文文档

在向服务器进行身份验证之前,如果需要上述终结点,则可以考虑添加。.requestMatchers("/csrf").permitAll()Spring中文文档

有关在应用程序启动时调用终结点的说明特定于客户端框架,因此不在本文档的讨论范围之内。/csrfSpring中文文档

虽然这需要对单页应用程序进行更改,但好处是 CSRF 令牌仅加载一次,并且令牌可以继续延迟。 此方法特别适用于使用延迟令牌并从中受益的应用程序,因为它允许在每个请求上不读取令牌。HttpSessionCsrfTokenRepositoryHttpSessionSpring中文文档

通过将 设置为 ,必须首先加载以确定要使用的属性名称。 这会导致在每个请求上加载 。csrfRequestAttributeNamenullCsrfTokenCsrfTokenSpring中文文档

防止 CSRF 违规

如果延迟加载CsrfToken的步骤适合您,那么您还可以选择使用以下配置加入Spring Security 6对BREACH保护的默认支持:CsrfTokenSpring中文文档

CsrfToken漏洞保护
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf");
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRequestHandler(requestHandler)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val requestHandler = XorCsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	requestHandler.setCsrfRequestAttributeName("_csrf")
	http {
		csrf {
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
	class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
	p:csrfRequestAttributeName="_csrf"/>

选择退出步骤

如果配置 CSRF BREACH 保护会给您带来麻烦,请查看以下方案以获得最佳选择退出行为:Spring中文文档

我正在使用 AngularJS 或其他 Javascript 框架

如果您使用 AngularJS 和 HttpClientXsrfModule(或其他框架中的类似模块)以及 ,您可能会发现自动支持不再起作用。CookieCsrfTokenRepository.withHttpOnlyFalse()Spring中文文档

在这种情况下,您可以配置Spring Security以验证cookie中的原始数据,同时使用具有委派的自定义自定义来保持CSRF BREACH 对响应的保护,如下所示:CsrfTokenCsrfTokenRequestHandlerSpring中文文档

配置 BREACH 保护以验证原始令牌CsrfToken
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
	XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
	// set the name of the attribute the CsrfToken will be populated on
	delegate.setCsrfRequestAttributeName("_csrf");
	// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
	// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
	CsrfTokenRequestHandler requestHandler = delegate::handle;
	http
		// ...
		.csrf((csrf) -> csrf
			.csrfTokenRepository(tokenRepository)
			.csrfTokenRequestHandler(requestHandler)
		);

	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
	val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
	val delegate = XorCsrfTokenRequestAttributeHandler()
	// set the name of the attribute the CsrfToken will be populated on
	delegate.setCsrfRequestAttributeName("_csrf")
	// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
	// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
	val requestHandler = CsrfTokenRequestHandler(delegate::handle)
	http {
		csrf {
			csrfTokenRepository = tokenRepository
			csrfTokenRequestHandler = requestHandler
		}
	}
	return http.build()
}
<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"
		request-handler-ref="requestHandler"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>

这是将 Spring Security 配置为使用使用 cookie 值的客户端应用程序的推荐方法,因为它继续允许响应返回 CSRF 令牌的随机值,以防应用程序返回 HTML 或其他响应,这些响应可能在您不知情的情况下容易受到 BREACH 的攻击。Spring中文文档

BREACH 保护的作用是保护令牌,当它包含在可以进行 GZIP 压缩的响应正文中时,通常不包括标头和 cookie。Spring中文文档

服务器返回的任何令牌值都可以由客户端应用程序成功使用,因为基础(原始)CSRF 令牌不会更改。 AngularJS(或类似)应用程序不需要在每次请求之前/之后刷新 CSRF 令牌。Spring中文文档

如果您只是希望完全退出 CSRF BREACH 保护,则接下来列出了该选项。Spring中文文档

出于其他原因,我需要选择退出 CSRF BREACH 保护

如果 CSRF BREACH 保护因其他原因而无法为您工作,您可以使用延迟加载 CsrfToken 部分中的配置选择退出。Spring中文文档

BREACH 保护的作用是保护令牌,当它包含在可以进行 GZIP 压缩的响应正文中时,通常不包括标头和 cookie。Spring中文文档

服务器返回的任何令牌值都可以由客户端应用程序成功使用,因为基础(原始)CSRF 令牌不会更改。 AngularJS(或类似)应用程序不需要在每次请求之前/之后刷新 CSRF 令牌。Spring中文文档

支持 WebSocket 的 CSRF BREACH

如果防止CSRF BREACH的步骤适用于正常的HTTP请求,并且您正在使用WebSocket Security支持,那么您还可以选择加入Spring Security 6对带有Stomp标头的BREACH 保护的默认支持。CsrfTokenSpring中文文档

WebSocket 安全漏洞保护
@Bean
ChannelInterceptor csrfChannelInterceptor() {
	return new XorCsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
	return XorCsrfChannelInterceptor()
}
<b:bean id="csrfChannelInterceptor"
	class="org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor"/>

如果为 WebSocket Security 配置 CSRF BREACH 保护会给您带来麻烦,您可以使用以下配置配置 5.8 默认值:Spring中文文档

默认配置 5.8 的 WebSocket 安全性
@Bean
ChannelInterceptor csrfChannelInterceptor() {
	return new CsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
	return CsrfChannelInterceptor()
}
<b:bean id="csrfChannelInterceptor"
	class="org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor"/>