Why is Spring Security redirecting to /error in this case?
Asked Answered
W

1

7

So I have a simple SecurityFilterChain configuration and a simple AuthenticationEntrypoint implementation.

The AutheticationEntrypoint implementation just writes a JSON response and the send a 403 error to the client.

The problem is that, for some reason, when I try access a protected resource without credentials, the request gets redirected to /error as I can see in the logs (DEBUG level).

SecurityConfiguration.java

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().disable()
            .httpBasic().disable()
            .csrf().disable()
            .headers()
                .frameOptions().sameOrigin()
                .and()
            .exceptionHandling()
                .authenticationEntryPoint(new ApplicationAuthenticationEntryPoint())
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .build();
    }
}

ApplicationAuthenticationEntryPoint.java

public class ApplicationAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.getWriter().write(mapper.createObjectNode()
                .put("timestamp", LocalDateTime.now().toEpochSecond(ZoneOffset.of("-3")))
                .put("message", "Access denied to resource")
                .toString());
    }
}

When I call a protected resource, I see the following in the logs:

2023-03-10T14:59:49.528-03:00 DEBUG 58236 --- [nio-8080-exec-8] o.s.security.web.FilterChainProxy        : Securing GET /test
2023-03-10T14:59:49.528-03:00 DEBUG 58236 --- [nio-8080-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2023-03-10T14:59:49.529-03:00 DEBUG 58236 --- [nio-8080-exec-8] o.s.security.web.FilterChainProxy        : Securing GET /error
2023-03-10T14:59:49.529-03:00 DEBUG 58236 --- [nio-8080-exec-8] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext

Why there's a line "Securing GET /error"? The Spring Security filter chain is called twice due to that "redirect". Including the ExceptionTranslationFilter. I put a breakpoint on the the ExceptionTranslationFilter and the debugger falls twice on this breakpoint.

Is this /error that default error page from Spring Boot? If so, how? Since all the security is made on filters, it hasn't reached the MVC stack yet by the time the authentication exception occurs.

Perhaps I'm lacking knowledge on how Spring Security works.

Why is this happening?

Weighbridge answered 10/3, 2023 at 18:2 Comment(5)
You are not logged in, so your ApplicationAuthenticationEntryPoint is executed. This class is returning an error (response.sendError(HttpServletResponse.SC_FORBIDDEN);). This error is redirected to /error (default error handler). You have to permit /error to see the error response.Coup
Have a look at #75272458Coup
@Coup . . . it really helped me. If I understood it correctly, seems like Spring Boot registers the BasicErrorController mapped to /error by default to the servlet container, tomcat in this case. When the SC_FORBIDDEN is sent in the response, tomcat delegate the global error page to the /error which then falls to the BasicErrorController to write the corresponding response.Weighbridge
To be sure, open the /error and you will see the error message. Maybe it is some other exception, I could not see in your code.Coup
Actually you're 100% correct. Permiting /error shows the default 403 response. Customizing the BasicErrorController was enough to deal with my case.Weighbridge
S
0

Regarding your question why this is happening:

It is the regular flow of events in Spring Boot in combination with Spring Security.

  1. You are not allowed to access the /test url anonimously
  2. Control is forwarded to the ApplicationEntryPoint, where you return the 403 error
  3. Tomcat and Spring Boot wrap your original http request and redirect you to the /error url
  4. If the /error url is protected by Spring Security you will be redirected to a login page

As already stated in the comments. You can overcome this flow by allowing access to the /error page in your SecurityFilterChain

 http.authorizeHttpRequests()
   .requestMatchers("/error").permitAll()
   .anyRequest().authenticated()
   .and()          

In this case you will get a 403 error without a redirect to the login page.


Alternatively you can also permit the `DispatcherType.ERROR` with the same effect.
 http.authorizeHttpRequests()
    .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()
    .anyRequest().authenticated()
    .and()     

There is/was another option to omit the second access check in the Spring Security AuthorizationFilter class by using the deprecated shouldFilterAllDispatcherTypes method.

http.authorizeHttpRequests()
    .shouldFilterAllDispatcherTypes(false)              
    .anyRequest().authenticated()
    .and()  

Another possibility is to disable the Spring MVC Error handling by using:
spring.autoconfigure.exclude=  org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
Syrinx answered 22/9 at 18:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.