[英]CSRF token not generated with Webflux
我有一个由 Spring Security 保护的 Webflux 应用程序,其中默认启用 CSRF 保护。 但是,我无法将 CSRF 令牌保存在会话中。
经过一番调查,我注意到它可能来自WebSessionServerCsrfTokenRepository.class
。 在这个类中,有一个generateToken
方法应该从一个生成的 CSRF 令牌创建一个 Mono:
public Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return Mono.fromCallable(() -> {
return this.createCsrfToken();
});
}
private CsrfToken createCsrfToken() {
return new DefaultCsrfToken(this.headerName, this.parameterName, this.createNewToken());
}
private String createNewToken() {
return UUID.randomUUID().toString();
}
但是,即使createCsrfToken
generateToken
CsrfWebFilter
并且我永远不会将 CSRF 令牌保存在会话中。 我的断点永远不会进入createCsrfToken
方法,这可能意味着它永远不会被订阅。
我正在使用 Spring Boot 2.1.0.RELEASE
和 Spring Security 5.1.1.RELEASE
在Netty
上运行。
我在一个仅包含以下依赖项的空示例应用程序上重现了该问题:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
我错过了什么还是 Spring Security 有问题?
更新
通过进一步调查,我认为问题来自 Spring Security CsrfWebFilter.class
中的这种方法:
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> {
Mono<CsrfToken> csrfToken = this.csrfToken(exchange);
exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
return chain.filter(exchange);
});
}
在这里,从未订阅csrfToken
Mono。 当我以这种方式重写过滤器时,我设法在会话中添加了令牌:
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> {
return this.csrfToken(exchange)
.map(csrfToken -> exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken))
.then(chain.filter(exchange));
});
}
但是,我的 Thymeleaf 模型中从未添加_csrf
参数,因此以下测试不起作用:
<form name="test-csrf" action="/test" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<button type="submit">Escape!</button>
</form>
如果有人遇到这个问题,我与 Spring Team 的某个人进行了讨论。
它实际上不打算直接订阅csrfToken
Mono,仅在需要时才这样做。 在应用程序中触发订阅是开发者的责任,有两种方法可以做到。
方法一:显式订阅。
在某些@ControllerAdvice或抽象控制器类中通过@ModelAttribute提供订阅:
@ModelAttribute(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME)
public Mono<CsrfToken> getCsrfToken(final ServerWebExchange exchange) {
return exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
}
方法二:使用 Thymeleaf 自动处理 CSRF。
确保您的 POM 中有以下依赖项以使用 Thymeleaf 和 Spring Security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
这将自动将 CSRF 令牌添加到您的模型中,并通过表单隐藏输入传递它(仍然需要将其添加到 POST Ajax 请求的标头中)。
有关更多信息,这是我在 Spring 打开的问题: https ://github.com/spring-projects/spring-security/issues/6046
当我检查exchange
的attributes
属性时,我看不到CsrfToken
那里,所以我无法订阅它。 在浏览器会话中检查 cookie 后,我确实看到了XSRF-TOKEN
。
我最终在我们的项目中使用它作为过滤器:
@Component
@Slf4j
class CsrfHeaderFilter implements WebFilter {
@Override
Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
def xsrfToken = exchange.getRequest().getCookies().getFirst("XSRF-TOKEN").value
exchange = exchange.mutate().request({
it.header("X-XSRF-TOKEN", xsrfToken)
}).build()
log.debug(xsrfToken)
chain.filter(exchange)
}
}
然后修改我的安全配置,使其将此过滤器放在CsrfWebFilter
之前:
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class WebSecurityConfiguration {
@Autowired
CsrfHeaderFilter csrfHeaderFilter
@Bean
SecurityWebFilterChain SecurityWebFilterChain(ServerHttpSecurity http) {
http
.addFilterBefore(csrfHeaderFilter, SecurityWebFiltersOrder.CSRF)
.httpBasic().disable()
.formLogin().disable()
.oauth2Login().and()
.csrf({
it.csrfTokenRepository(new CookieServerCsrfTokenRepository())
})
.authorizeExchange()
.pathMatchers("/actuator/health").permitAll()
.pathMatchers("/**").authenticated()
.and().build()
}
}
这对我们有用,因为CsrfWebFilter
期望找到令牌的地方之一是在请求标头X-XSRF-TOKEN
中。
在 Springboot 应用程序类中创建 webfilter bean,如下所示。
@Bean
public WebFilter addCsrfTokenFilter() {
return (exchange, next) -> Mono.just(exchange)
.flatMap(ex -> ex.
<Mono<CsrfToken>>getAttribute(CsrfToken.class.getName()))
.doOnNext(ex -> {
})
.then(next.filter(exchange));
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.