1

I have the following WebFilter implementation:

@Component
@RequiredArgsConstructor
public class AccountTokenFilter implements WebFilter {
    private final TokenService tokenService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<Authentication> authMono = tokenService.verifyToken(exchange.getRequest())
                .map(AccountTokenAuthentication::new);
        return authMono.hasElement().flatMap(authenticated -> {
            if (authenticated) {
                return authMono.flatMap(auth -> chain.filter(exchange)
                        .contextWrite(context -> ReactiveSecurityContextHolder.withAuthentication(auth)));
            } else {
                return chain.filter(exchange);
            }
        });
    }
}

The idea is that it does the following:

  1. Try to verify the request's authentication token and return the account it's linked to.
  2. If the account exists, add it to the security context.
  3. Continue the filter chain with chain.filter(exchange)

The problem is that somehow, the initial authMono is getting executed 4 times for each request! Ideally this should be executed only once, but I'm at a loss for how I can even begin to debug this.

If it may help, here is my filter chain configuration:

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .csrf().disable()
            .cors().and()
            .httpBasic().disable()
            .formLogin().disable()
            .logout().disable()
            .authorizeExchange(spec -> spec
                    .pathMatchers(HttpMethod.POST, "/accounts", "/tokens").permitAll()
                    .pathMatchers(HttpMethod.GET, "/tokens/verify").permitAll()
                    .anyExchange().authenticated()
            )
            .addFilterAt(accountTokenFilter, SecurityWebFiltersOrder.AUTHENTICATION);
        return http.build();
    }
Andrew Lalis
  • 894
  • 3
  • 15
  • 26

2 Answers2

0

I was able to solve this by adding a switchIfEmpty and a sort of "fake" authentication to use if we couldn't really obtain one.

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    return tokenService.verifyToken(exchange.getRequest())
            .map(AccountTokenAuthentication::new)
            .switchIfEmpty(Mono.just(new AccountTokenAuthentication(null)))
            .flatMap(auth -> chain.filter(exchange)
                    .contextWrite(context -> ReactiveSecurityContextHolder.withAuthentication(auth)));
}

The filter is still being executed twice, but that's better than 4 times like before.

Andrew Lalis
  • 894
  • 3
  • 15
  • 26
0

There are two duplications happening here:

First, you are building upon Mono authMono twice: one at authMono.hasElement() and one at authMono.flatMap() inside a flatMap. Splitting your Mono like that will cause the Mono to be evaluated twice.

Luckily you don't have to evaluate the Mono twice in your case, with Flux.switchIfEmpty you can switch to an alternative Publisher if the Mono has no elements:

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    return tokenService.verifyToken(exchange.getRequest())
            .map(AccountTokenAuthentication::new)
            .flatMap(auth -> chain.filter(exchange)
                    .contextWrite(context ->  ReactiveSecurityContextHolder.withAuthentication(auth)))
            .switchIfEmpty(chain.filter(exchange));
}

Second, Filters are automatically registered in Spring Boot. By turning your Filter into a Bean with @Component, the Filter is registered twice and also executed twice. Since you already register your Filter in your filter chain configuration, you should disable auto-registration by creating this bean (copied from the official documentation):

@Bean
public FilterRegistrationBean registration(MyFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}
Patrick Hooijer
  • 658
  • 4
  • 7