Search code examples
javaspring-bootspring-security

Security filter configuration not working after upgrade to Spring Boot 3


I'm upgrading from Spring Boot 2.7.5 to 3.0.11, and with that there are several changes in SecurityFilter configurations. I'm able to convert my existing filter class to Spring Boot 3.x specifics however, I'm getting the below error while running the app:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [.../configuration/WebSecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' threw exception with message: Web security configuration error: This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).

This is because there is more than one mappable servlet in your servlet context: {org.springframework.web.servlet.DispatcherServlet=[/], org.h2.server.web.JakartaWebServlet=[/h2-console/*]}.

This is my config class:

@Configuration
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig {

    private static final String[] AUTH_WHITELIST = {
            // -- Swagger UI v2
            "/v2/api-docs",
            "v2/api-docs",
            "/swagger-resources",
            "swagger-resources",
            "/swagger-resources/**",
            "swagger-resources/**",
            "/configuration/ui",
            "configuration/ui",
            "/configuration/security",
            "configuration/security",
            "/swagger-ui.html",
            "swagger-ui.html",
            "webjars/**",
            // -- Swagger UI v3
            "/api/template/v3/api-docs/**",
            "v3/api-docs/**",
            "/api/template/swagger-ui/**",
            "swagger-ui/**",
            // Actuators
            "/actuator/**",
            "/health/**"
    };

    /**
     * Configures access to application with reduced requirements to security
     * to allow local testing and h2 console.
     *
     * @param http security object
     * @return instance of {@link SecurityFilterChain}
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        try {
            http
                    .csrf(AbstractHttpConfigurer::disable)
                    .authorizeHttpRequests(auth -> auth
                            .requestMatchers(AUTH_WHITELIST).permitAll()
                            .anyRequest().authenticated()
                    )
                    .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
                    .httpBasic(AbstractHttpConfigurer::disable) // disables pop-up
                    .formLogin(AbstractHttpConfigurer::disable)
                    .headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer
                                    .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) // to make accessible h2 console, disables xframe deny warnings
                    .cors(); // uses cors settings - only disabled if WebConfigLocal running
            return http.build();
        } catch (Exception ex) {
            throw new GenericRuntimeException(buildMessage(ERROR_WEB_SECURITY_FILTER.getText(ex.getMessage())), ex);
        }
    }
}

Can anyone help understand what went wrong? I'm not able to understand anything from the error.


Solution

  • Starting with Spring Security version 6.x it is no longer possible to demand the MvcRequestMatcher fluently. Instead, the desired patterns are passed to a general #requestMatchers method, which by default uses MvcRequestMatcher for mapping behind the scenes. AntPathRequestMatcher must now be explicitly passed to this #requestMatchers method if necessary.

    A discovered security vulnerability CVE-2023-34035 shows that if more than one mappable servlet is secured by Spring Security, misconfigurations can occur. Therefore, as of version 6.1.2, in such a case of two servlets, the RequestMatcher must be specified explicitly.

    That is what seems to be your problem. For example, the H2 database places its own dedicated JakartaWebServlet in the context for its endpoint, what forces us to explicitly specify MvcRequestMatcher for all endpoints handled by Spring's DispatcherServlet.

    As explained here cve-2023-34035-mitigations you can do as follows:

    1. provide the following bean:
    @Bean
    public MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
        return new MvcRequestMatcher.Builder(introspector);
    }
    
    1. adapt your filter chain:
    @Bean
    public SecurityFilterChain filterChain(MvcRequestMatcher.Builder mvc, HttpSecurity http) throws Exception {
        try {
            http
                    .csrf(AbstractHttpConfigurer::disable)
                    .authorizeHttpRequests(auth -> auth
                            .requestMatchers(mvc.pattern(AUTH_WHITELIST)).permitAll()
                            .anyRequest().authenticated()
                    )
                    .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
                    .httpBasic(AbstractHttpConfigurer::disable) // disables pop-up
                    .formLogin(AbstractHttpConfigurer::disable)
                    .headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer
                                    .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) // to make accessible h2 console, disables xframe deny warnings
                    .cors(); // uses cors settings - only disabled if WebConfigLocal running
            return http.build();
        } catch (Exception ex) {
            throw new GenericRuntimeException(buildMessage(ERROR_WEB_SECURITY_FILTER.getText(ex.getMessage())), ex);
        }
    }
    

    If this won't fix your problem you might wanna set up a dedicated filter chain for the H2 db as well, however I don't think that this would be necessary.