Why is Spring ResponseStatusException 400 translated into 403

Viewed 50

I've got a Spring boot application with this security config:

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .headers().frameOptions().sameOrigin().and()
        .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
        .authorizeRequests()
            .antMatchers("/api/authenticate/**", "/h2-console/**").permitAll()
            .anyRequest().authenticated();
  }

And in my application code I throw a ResponseStatusException:

    if (existingTenant.isPresent()) {
      throw new ResponseStatusException(
          HttpStatusCode.valueOf(400),
          "Tenant with name " + tenant.name() + " already exists");
    }

But the api response I get is a 403:

* Mark bundle as not supporting multiuse
< HTTP/1.1 403 
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: SAMEORIGIN
< Content-Length: 0
< Date: Sat, 13 Aug 2022 19:26:27 GMT
< 
* Connection #0 to host localhost left intact

The only logging I see is:

2022-08-13T12:32:09.260-07:00 WARN 79360 --- [nio-8080-exec-6] .w.s.m.a.ResponseStatusExceptionResolver : Resolved [org.springframework.web.server.ResponseStatusException: 400 BAD_REQUEST "Tenant with name tenant1 already exists"]

Why isn't the response getting the 400 response code I set in the exception?

1 Answers

The issue was that spring boot comes with ErrorMvcAutoConfiguration enabled. The 403 error I was seeing was because the default behavior when an exception is thrown is to serve the /error page as an anonymous user. In my opinion this interacts poorly with how a new user would configure their web security. I was able to fix the issue by changing my security configuration to allow anonymous access to the /error page.

I was only able to figure out what was going wrong by adding breakpoints in AffirmativeBased#decide and inspecting the request. I noticed that the request was to /error and not to the original url, so I was able to make some educated guesses about what was happening.

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .headers().frameOptions().sameOrigin().and()
        .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
        .authorizeRequests()
            .antMatchers("/api/authenticate/**", "/h2-console/**").permitAll()
            // ---------------------------------------------
            .antMatchers("/error").anonymous() // <----- Fix
            .anyRequest().authenticated();
  }

Another approach you can take for dealing with this issue is to disable the auto-configuration by adding this annotation wherever you have @SpringBootApplication:

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})
Related