javaspring-bootspring-securityspring-webfluxspring-reactive

Using spring SecurityWebFilterChain how to disable/block all non-https requests except few known paths


I am using Spring security within a Spring Boot Webflux application to serve traffic primarily on HTTPS port. However as an operational requirement I need to support couple of non-secure REST API paths in my Spring Boot application for health check etc that need to be exposed on HTTP as well.

So how do I enforce all the requests to HTTPS except for a known path using SecurityWebFilterChain bean?

This is how I have defined my SecurityWebFilterChain bean:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {    
    @Bean
    SecurityWebFilterChain webFilterChain( ServerHttpSecurity http )
     throws Exception {
         return http 
            .authorizeExchange(exchanges -> exchanges
                    .anyExchange().permitAll()
                    .and()
                    .exceptionHandling()
                    .authenticationEntryPoint((exchange, exception) ->
                        Mono.error(exception))
                    )
            .csrf().disable()
            .headers().disable()
            .logout().disable()
            .build();
    }
}

This obviously won't work as intended because it is allowing all requests to use HTTP and HTTPS schemes whereas I want to always enforce HTTPS except for a path e.g. /health.

Please suggest what changes would I need in above code to get this done.


Solution

  • Here is what I came up with to to solve this problem. I am calling a custom matcher in .matchers( customMatcher ) method

    @Configuration
    @EnableWebFluxSecurity
    public class SecurityConfig {
    
        private static final Set<String> UNSECURED = 
                     Set.of ( "/health", "/heartbeat" );
    
        @Bean
        SecurityWebFilterChain webFilterChain( final ServerHttpSecurity http ) {    
            return http
                    .authorizeExchange(
                            exchanges -> exchanges
                            .matchers( this::blockUnsecured ).permitAll()
                            .and()
                            .exceptionHandling()
                            .authenticationEntryPoint(
                                   (exchange, exception) -> Mono.error(exception))
                            )
                    .csrf().disable()
                    .headers().disable()
                    .logout().disable()
                    .httpBasic().disable()
                    .build();
        }
    
        Mono<MatchResult> blockUnsecured( final ServerWebExchange exchange ) {    
            // Deny all requests except few known ones using "http" scheme
            URI uri = exchange.getRequest().getURI();
    
            boolean invalid = "http".equalsIgnoreCase( uri.getScheme() ) &&
                    !UNSECURED.contains ( uri.getPath().toLowerCase() );    
            return invalid ? MatchResult.notMatch() : MatchResult.match();    
        }
    }
    

    Not sure if there is a better way of doing same.