Why is Spring ResponseStatusException 400 translated into 403
Asked Answered
D

2

10

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?

Diffident answered 13/8, 2022 at 19:35 Comment(2)
I call the endpoint successfully and get a 200 response when the exception is not thrown. Also, the logging shows that this particular exception is being thrown. Take a look at the message in the log - it matches my code. Any exception that is thrown gets converted into a 403 response somehow.Diffident
You didnt understand... Spring security works "before" the web mvc layer. So you web layer returns 400, the flow reaches the spring security, one spring security interceptors might execute (this is my assumltion) and imagine it also throws an exception, its a code... so the exception from the web request might be "swallowed" and 413 returns to the client instead. Try to disable slring secuirty for your flow for now (you're debugging anyway) and check whether 400 still gets translated to 413 or notLinesman
D
8

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})
Diffident answered 13/8, 2022 at 22:10 Comment(1)
Note that another good approach instead of breakpoints is to enable trace logging (logging.level.org.springframework.security=trace), which allows you to investigate the flow through various Spring Security filters, authentication providers and exception handling more easily. This has been a bit of an issue for a while. See #11623 which has links to related issues.Kaylil
C
8

For anyone who is experiencing this issue in spring boot 3 and previous answer doesn't work, I found solution in docs:

In short, you must allow DispatcherType.ERROR to pass the filtering by:

http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
    )

Cressler answered 22/8, 2023 at 9:5 Comment(1)
Why is this not the accepted answer? This completely nailed it for me. Thank you so much!! :DSher

© 2022 - 2024 — McMap. All rights reserved.