Describe the bug
I read #7719 and added a SubscribeCsrfTokenWebFilter that actively subscribe Mono<CsrfToken>.
However, if Mono<CsrfToken> is subscribed to after going through the SubscribeCsrfTokenWebFilter and during view rendering, the CSRF token will regenerated.
To Reproduce
The following code can reproduce the problem.
@Bean
public WebFilter subscribeCsrfTokenWebFilter() {
return ((exchange, chain) -> {
Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
CsrfToken token = csrfToken.block(); // Generate CsrfToken.
return csrfToken // Generate new CsrfToken!
.then(chain.filter(exchange));
});
}
If the ServerHttpRequest did not have a CSRF token, the
Each time Mono<CsrfToken> is subscribed, the repository generates and stores a token.
Expected behavior
The CSRF token should be generated only once during a single request.
Sample
See To Reproduce
Details
The CsrfWebFilter stores Mono<CsrfToken> in ServerWebExchange attributes that executes ServerCsrfTokenRepository#generateToken(ServerWebExchange ) and ServerCsrfTokenRepository#saveToken(ServerWebExchange , CsrfToken) when the request did not have a CSRF token.
https://github.com/spring-projects/spring-security/blob/dbce9b5b66b6a8395b217c58e349dca3379accd1/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java#L159-L174
This Mono<CsrfToken> repeats the generation and saving of a new token for each subscription.
CookieCsrfTokenRepository adds Set-Cookie: XSRF-TOKEN=... to the response header as many times as it is called.
Set-Cookie should be an override action, so all but the last CSRF token subscribed to in the application will be disabled.
Comment From: tt4g
I believe that adding Mono#cache() to CsrfWebFilter#generateToken(ServerWebExchange) will get around this problem.
Is there any reason why this can't be done?
private Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
return this.csrfTokenRepository.generateToken(exchange)
- .delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));
+ .delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token))
+ .cache();
}
Comment From: jzheaux
Hi, @tt4g, thanks for the suggestion. Would you be able to submit a PR along those lines, including a test?
Comment From: tt4g
PR #9760