Spring-security: CsrfWebFilter not generating the CSRF token with Webflux

Created on 5 Nov 2018  路  7Comments  路  Source: spring-projects/spring-security

Summary

I have a Spring Webflux application secured by Spring Security with CSRF protection enabled by default. In this application, I can't get the CSRF token to be saved in the Websession nor added in the model.

Actual Behavior

After some investigations, I noticed that the problem comes from Spring Security's CsrfWebFilter.class, in which there is the following method:

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);
        });
    }

In this method, the CsrfToken Mono is never subscribed, which prevents the token to be generated and added in the Websession.

Moreover, when my page is rendered, the _csrf parameter is null in the view model.

Expected Behavior

The CsrfToken Mono should be subscribed so that the WebSessionServerCsrfTokenRepository could generate and save the token in the Websession. The _csrf parameter should be accessible from the view model (maybe this one is an issue with Thymeleaf).

Workaround

As a workaround, I rewrite the CsrfWebFilter in my application and just override the above method this way:

private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> {
            return this.csrfToken(exchange)
                    .doOnNext(csrfToken -> exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken))
                    .then(chain.filter(exchange));
        });
    }

Then, to be able to retrieve the _csrf parameter in the model, I add this method in an abstract controller:

@ModelAttribute("_csrf")
    public CsrfToken csrfToken(final ServerWebExchange exchange) {

        return exchange.getAttribute(CsrfToken.class.getName());
    }

Is it a valid workaround?

Configuration

Here is my Security configuration class:

@EnableWebFluxSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(final ServerHttpSecurity http) {

        http.authorizeExchange()
                .pathMatchers("/**").permitAll()
                .and()
                .oauth2Login()
                .and()
                .oauth2Client();

        return http.build();
    }

    @Bean
    public ServerOAuth2AuthorizedClientRepository authorizedClientRepository(
            final ReactiveOAuth2AuthorizedClientService authorizedClientService) {

        return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
    }
}

Version

My application runs on Netty with Spring Boot 2.1.0.RELEASE, Spring Security 5.1.1.RELEASE, and Thymeleaf 3.0.10.RELEASE.

Most helpful comment

Is there a way to solve this problem if you are using functional framework in Spring 5? The ControllerAdvice doesn't seem to have any effect if you aren't actually defining Controllers.

All 7 comments

Thanks for the report.

This is intentional behavior because we don't want to create the token (and thus a session) unless it is necessary.

If you are fine with creating the token eagerly, the recommended workaround is to use ControllerAdvice. Alternatively, newer versions of Thymeleaf automatically subscribe and no additional work is necessary.

I'm going to close this as working as designed. If you find that you have additional questions/concerns, please let me know.

Thanks for your reply.

I didn't manage to subscribe automatically via Thymeleaf, or even make the CsrfRequestDataValueProcessor handling it automatically. But anyway, it's OK for me to do it via a model attribute, thank you.

@adsanche This is interesting that it isn't working. Can you put together a sample that reproduces the issue and post to a new ticket? The sample I linked to has tests and is working fine so there must be something strange happening.

@adsanche

I put together a sample of the Thymeleaf integration working here https://github.com/rwinch/spring-security-sample/tree/gh-6046

Make sure you have the correct dependencies (both spring-boot-starter-thymeleaf and thymeleaf-extras-springsecurity5).

Make sure you use th:action for your form.

The test demos that it is working

@rwinch I managed to make it work automatically based on your sample project.

I was actually missing the _thymeleaf-extras-springsecurity5_ dependency to trigger the addition of the CSRF parameter in the model.

This way, I still have to add my token explicitly in the request headers for the Ajax requests, but I can remove the hidden parameter from the forms and the model attribute in the controller advice.

Thanks for your feedbacks and explanations, hope it might help other people.

Is there a way to solve this problem if you are using functional framework in Spring 5? The ControllerAdvice doesn't seem to have any effect if you aren't actually defining Controllers.

In case one does not have a controller, the simplest workaround is to implement a custom WebFilter which places the Mono<CsrfToken> inside of the filter chain like so:

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    Mono<CsrfToken> token = (Mono<CsrfToken>) exchange.getAttributes().get(CsrfToken.class.getName());
    if (token != null) {
        return token.flatMap(t -> chain.filter(exchange));
    }
    return chain.filter(exchange);
}
Was this page helpful?
0 / 5 - 0 ratings