Search code examples
spring-security

Why am I getting a 403 from Spring-Security when setting permitAll?


I have the following code:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authz) -> authz
            .requestMatchers("/actuator/**").permitAll()
            .requestMatchers("/h2-console/**").permitAll()
            .anyRequest().authenticated()
    );
    http.csrf(AbstractHttpConfigurer::disable);
    http.headers((headers) -> headers
            .frameOptions((frame) -> frame
                    .disable()
            )
    );
    return http.build();
}

but when I attempt to hit the h2-console endpoint, I receive a 403 error:

curl -v http://localhost:8080/h2-console
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
...
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 

If I modify the authenticated to permitAll, i.e., .anyRequest().permitAll(), it functions perfectly fine.

How can I configure the code to restrict access to h2-console and all its sub-paths but is blocked for all other paths?

Update

Per the suggestion I tried this...

@Configuration
public class AppConfig {
    private static RequestMatcher h2ConsoleRequestMatcher() {
        return new RequestMatcher() {
            @Override
            public boolean matches(HttpServletRequest request) {
                String path = request.getServletPath();
                return path.startsWith("/h2-console");
            }
        };
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // TODO: 2021-10-13 - remove permitAll() and add proper security
        http.authorizeHttpRequests((authz) -> authz
                .requestMatchers("/actuator/**").permitAll()
                .requestMatchers("/h2-console/**").permitAll()
                .anyRequest().authenticated()
        );
        http.csrf((csrf) ->
                csrf.ignoringRequestMatchers(h2ConsoleRequestMatcher())
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        );
        http.headers((headers) -> headers
                .frameOptions(
                        HeadersConfigurer.FrameOptionsConfig::disable
                )
        );
        return http.build();
    }
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowCredentials(true);
        corsConfig.addAllowedOrigin("http://localhost:3000");
        corsConfig.addAllowedHeader("*");
        corsConfig.addAllowedMethod("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);

        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

But I still get the 403...

curl http://localhost:8080/h2-console -v
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /h2-console HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.87.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 

Thanks to the accepted answer below here is the final working version

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authz) -> authz
                .requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll()
                .anyRequest().authenticated()
        );
        http.csrf((csrf) ->
                csrf.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**"))
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        );
        http.headers((headers) -> headers
                .frameOptions(
                        HeadersConfigurer.FrameOptionsConfig::disable
                )
        );
        return http.build();
    }

Solution

  • By default, requestMatchers("a_string_path") will map to a MvcRequestMatcher

    If the HandlerMappingIntrospector is available in the classpath, maps to an MvcRequestMatcher that does not care which HttpMethod is used. This matcher will use the same rules that Spring MVC uses for matching. For example, often times a mapping of the path "/path" will match on "/path", "/path/", "/path.html", etc. If the HandlerMappingIntrospector is not available, maps to an AntPathRequestMatcher. If a specific RequestMatcher must be specified, use requestMatchers(RequestMatcher...) instead Params: patterns – the patterns to match on. The rules for matching are defined by Spring MVC if MvcRequestMatcher is used Returns: the object that is chained after creating the RequestMatcher. Since: 5.8

    So for accessing the h2-console with its own Servlet, you need to use an AntPathRequestMatcher

    This should work:

    .requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll()
    

    An alternative is to provide a extra WebSecurityCustomizer bean.

    @Bean
    //@Profile("dev")
    WebSecurityCustomizer h2ConsoleSecurityCustomizer() {
     return web -> 
          web.ignoring().requestMatchers(new AntPathRequestMatcher("/h2-console/**"));
    }