spring-bootspring-securityspring-security-oauth2spring-filter

How to stop processing request any further?


I need to prevent all request processing if a certain header is not present in the request. So, I have the following SecurityConfig code where I have configured a filter that executes before everything else:

protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
    httpSecurity.addFilterBefore(
        new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                    throws ServletException, IOException {    
                if(testmode) {
                    String testModeHeader = request.getHeader("TestMode");
                    System.out.println("In testmode :"+request.getRequestURI()+" "+testModeHeader);

                    if(!testmodeHeaderValue.equals(testModeHeader)) {
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                        response.flushBuffer();
                        return;
                    }
                }
                chain.doFilter(request, response);
            }
        }
        , SecurityContextPersistenceFilter.class);
    
    httpSecurity.csrf().disable()
        .authorizeRequests().antMatchers("/oauth/**", "/oauth2/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .oauth2Login()
        .userInfoEndpoint()
            .userService(oauthUserService)
        .and()
        .successHandler(new AuthenticationSuccessHandler() {
            
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 
                    Authentication authentication) throws IOException, ServletException {
                ...code not shown...
            }
        })
        .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

In spite of sending the error code and committing the response using flushBuffer, it seems spring boot is still redirecting the user to the login page as evidenced by the following output:

In testmode :/token null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null
In testmode :/oauth2/authorization/google null

What do I need to do to just commit the response without going through any of the rest of the filters?

And I am not sure what it is getting so many requests to /oauth2/authorization/google when I made only one request to /token ?


Solution

  • The behavior you're seeing is the attempted rendering of the default error view (/error) combined with the secure by default principle (see Example 86) employed by Spring Security.

    Since you're returning a 401 status code in the response, the /error view is being invoked internally, which results in a second pass through the filter chain. Since your filter extends OncePerRequestFilter, it is not invoked the 2nd time through, making it appear to be skipped.

    Subsequently, the rest of the Spring Security filter chain kicks in, detects an unauthorized access of the /error view, and redirects to /login, or in your case since you're using an oauth2 dependency, /oauth2/authorization/google page. The cycle then continues ad infinitum.

    A simple workaround in this case would be to expose the error page, via .mvcMatchers("/error").permitAll() or similar. It's worth considering whether this makes sense though, or whether you're exposing sensitive information by doing so. You may want to employ another technique for handling certain errors, such as the ErrorViewResolver interface in Spring Boot.