【发布时间】:2019-04-08 04:51:28
【问题描述】:
我有一个由 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();
}
但是,即使CsrfWebFilter 调用了generateToken 方法,也永远不会调用createCsrfToken 方法,并且我从未获得要保存在会话中的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));
});
}
但是,_csrf 参数从未添加到我的 Thymeleaf 模型中,因此以下测试不起作用:
<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>
【问题讨论】:
-
好吧,确实看起来您从未订阅过
generateToken()电话。如果您在服务或配置中使用此方法,请尝试在调用该方法时添加.block()。这肯定会执行createNewToken(),但是.block()被认为是不好的做法。 -
这段代码其实来自WebSessionServerCsrfTokenRepository.class,它是一个Spring Security类,我没有直接使用。
标签: java spring spring-boot spring-security spring-webflux