CSRF 保护与 Spring Security 6 不起作用



我将我的项目升级到 Spring Boot 3 和 Spring Security 6,但自从升级以来,CSRF 保护不再有效。

我使用以下配置:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated())
.httpBasic(withDefaults())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder().username("user").password("{noop}test").authorities("user").build();
return new InMemoryUserDetailsManager(user);
}

在我的网页上,我只有一个按钮:

<button id="test">Test CSRF</button>

还有下面的 JavaScript 代码:

document.querySelector("#test").addEventListener('click', async function() {
console.log('Clicked');
// This code reads the cookie from the browser
// Source: https://stackoverflow.com/a/25490531
const csrfToken = document.cookie.match('(^|;)\s*XSRF-TOKEN\s*=\s*([^;]+)')?.pop();
const result = await fetch('./api/foo', {
method: 'POST',
headers: {
'X-XSRF-Token': csrfToken
}
});
console.log(result);
});

在 Spring Boot 2.7.x 中,此设置工作正常,但是如果我将项目升级到 Spring Boot 3 和 Spring Security 6,则会出现 403 错误,并显示以下调试日志:

15:10:51.858 D         o.s.security.web.csrf.CsrfFilter: Invalid CSRF token found for http://localhost:8080/api/foo
15:10:51.859 D   o.s.s.w.access.AccessDeniedHandlerImpl: Responding with 403 status code

我的猜测是这与#4001的变化有关。但是我不明白我必须对我的代码进行哪些更改,或者我是否必须对某些内容进行 XOR 操作。

我确实检查了这是否是由于CSRF令牌的新延迟加载,但是即使我第二次单击该按钮(并验证XSRF-TOKEN cookie是否已设置),它仍然不起作用。

我最近在参考文档中添加了迁移到 5.8(准备到 6.0)的部分,演示了此问题的解决方案。

TL;DR参见 我正在使用 AngularJS 或其他 Javascript 框架。

这里的问题是AngularJS(以及上面的示例代码)直接使用XSRF-TOKENcookie。在Spring Security 6之前,这很好。但不幸的是,cookie 实际上是用来持久化原始令牌的,而在 Spring Security 6 中,默认情况下不接受原始令牌。理想情况下,前端框架将能够使用另一个源来获取令牌,例如X-XSRF-TOKEN响应标头。

但是,即使使用 Spring Security 6,也不会提供开箱即用的响应标头,尽管它可能是一个值得建议的增强功能。我还没有建议这样的增强功能,因为Javascript框架默认情况下无法使用它。

现在,您需要通过将 Spring Security 6 配置为接受原始令牌来解决此问题,如我上面链接的部分所建议的那样。该建议允许提交原始令牌,但继续使用该XorCsrfTokenRequestAttributeHandler来提供请求属性的哈希版本(例如request.getAttribute(CsrfToken.class.getName())request.getAttribute("_csrf")),以防万一有任何内容将 CSRF 令牌呈现为 HTML 响应,这可能容易受到破坏。

我建议找一个信誉良好的来源来更彻底地研究 BREACH ,但不幸的是,我不能声称自己是这样的来源。

我还建议现在关注Spring Security问题,因为一旦社区开始使用Spring Security 6,情况可能会迅速改变。您可以使用此过滤器作为跟踪 CSRF 相关问题的可能方法。

我们有一个带有弹簧靴的角度应用。我们尝试迁移到 spring-boot 3(Spring Security 6)。我们面临着同样的问题。

我们尝试了许多方法,包括这个问题答案中的一些解决方案,但我们失败了。经过一段时间,我们从春季安全文档中找到了解决方案。

我们需要做的是,在配置中设置nullCsrfRequestAttributeName

requestHandler.setCsrfRequestAttributeName(null);

实际发生了什么:
默认情况下,CsrfToken 将在 Spring Security 版本 5 中的每个请求上加载。这意味着在典型设置中,每个请求(即使是那些不必要的请求)都必须读取 HttpSession。

Spring Security 6的默认行为是推迟查找CsrfToken,直到需要它。

我们的应用程序每次都需要令牌。因此,我们需要选择加入 5.8 默认值。

示例代码如下(来自文档):

@Bean
DefaultSecurityFilterChain springSecurity(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
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(requestHandler)
);
return http.build();
}

我目前通过禁用XorCsrfTokenRequestAttributeHandler来解决此问题,如下所示:

.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// Added this:
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))

但是,这意味着我可能容易受到BREACH 攻击。

谢谢你!我能够使用它来解决JHipster + Spring Boot 3应用程序中的类似项目。但是,类名似乎最近可能发生了变化。这是我必须使用的:

.csrf(csrf -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler()))

从 Spring Security 6.0.1 和 Spring Boot 3.0.2 开始,按照接受答案中的说明在第一个请求时失败,但此后成功。它在第一个请求上失败的原因是,在调用受保护的方法之前,永远不会创建令牌的 cookie。这是因为该方法CookieCsrfTokenRepository.saveToken仅在CsrfFilter调用deferredCsrfToken.get()时才被调用,而 仅在 POST、PUT、PATCH 和 DELETE 方法上调用。不幸的是,在当前的实现下,这意味着客户端必须预期第一个请求会失败。在以前版本的Spring Security下,我们曾经能够指望令牌的cookie包含在对GET,HEAD或OPTIONS请求的响应中。

@steve-reisenberg 指出的文档适用于 servlet。

以下是对 webflux 应用程序的改编(如spring-cloud-gateway):

http.csrf((csrf) -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle));

这应该防止CSRF和BREACH(而不是引用(服务器)CsrfTokenRequestAttributeHandler的答案,这些答案暴露在BREACH中)。

使用接受的答案会中断需要使用 Spring SecuritySecurityMockMvcRequestPostProcessors.crsf()需要 CSRF 的测试,我只能使用CsrfTokenRequestAttributeHandlerXorCsrfTokenRequestAttributeHandler在 Spring Boot 的 CSRF 配置中,两者都给出了积极的测试结果。

使用公认的答案使 Angular 工作但会破坏测试。

因此,目前唯一的解决方法似乎是使用CsrfTokenRequestAttributeHandler,从而有效地禁用Spring Security的违规保护。

我已经为不同的场景创建了一个问题,您需要通过JavaScript发送带有标头的CSRF令牌,这在文档中并不清楚。如果你有一个多页面的应用程序,比如你主要在 HTML 中挂载 React 组件的应用程序,它可能对你有用。

基本上你需要的是使用默认CSRF配置的X-CSRF-TOKEN标头。

https://github.com/spring-projects/spring-security/issues/13009

这就是它对我有用的

CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
delegate.setCsrfRequestAttributeName(null);
// Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
// default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
CsrfTokenRequestHandler requestHandler = delegate::handle;
...
.csrf((csrf) -> csrf
.csrfTokenRepository(tokenRepository)
.csrfTokenRequestHandler(requestHandler)
)

相关内容

最新更新