spring-boot Error: Exceeded maxRedirects. Probably stuck in a redirect loop

Viewed 9451

I am trying to perform JWT auth in spring boot and the request are getting stuck in redirect loop.

JWTAuthenticationProvider

@Component
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean supports(Class<?> authentication) {
        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
        String token = jwtAuthenticationToken.getToken();

        JwtParsedUser parsedUser = jwtUtil.parseToken(token);

        if (parsedUser == null) {
            throw new JwtException("JWT token is not valid");
        }
        UserDetails user = User.withUsername(parsedUser.getUserName()).password("temp_password").authorities(parsedUser.getRole()).build();
        return user;
    }

JwtAuthenticationFilter

public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/**");
        this.setAuthenticationManager(authenticationManager);
    }

    @Override
    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        return true;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {

        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            throw new JwtException("No JWT token found in request headers");
        }

        String authToken = header.substring(7);

        JwtAuthenticationToken authRequest = new JwtAuthenticationToken(authToken);

        return getAuthenticationManager().authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,

            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        super.successfulAuthentication(request, response, chain, authResult);

        chain.doFilter(request, response);
    }

}

SecurityConfiguration

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(jwtAuthenticationProvider);

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable().authorizeRequests().antMatchers("/secured-resource-1/**", "/secured-resource-2/**")
                .hasRole("ADMIN").antMatchers("/secured-resource-2/**").hasRole("ADMIN").and().formLogin()
                .successHandler(new AuthenticationSuccessHandler()).and().httpBasic().and().exceptionHandling()
                .accessDeniedHandler(new CustomAccessDeniedHandler()).authenticationEntryPoint(getBasicAuthEntryPoint())
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(authenticationManager()),
                        FilterSecurityInterceptor.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
        return new CustomBasicAuthenticationEntryPoint();
    }
}

MainController

@RestController
public class MainController {
    @Autowired
    private JwtUtil jwtUtil;

    @GetMapping("/secured-resource-1")
    public String securedResource1() {
        return "Secured resource1";
    }
}

When I hit the endpoint with the valid JWT token, the code goes in a loop from Filter to provider class and ends in Error:

Exceeded maxRedirects. Probably stuck in a redirect loop http://localhost:8000/ error. 

Debug logs shows the following error:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot call sendError() after the response has been committed] with root cause

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

Any suggestions what am I missing here. Thanks in advance.

2 Answers

I believe the the reason for this is because you have not actually set the AuthenticationSuccessHandler for the bean JwtAuthenticationFilter, since it is not actually set it will keep looping around super and chain and later when the error needs to be sent since response is already written in super() chain.doFilter will fail because once the response is written it cannot be again written hence the error call sendError() after the response has been committed.

To correct this in your SecurityConfiguration before setting this

.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()),
                        FilterSecurityInterceptor.class)

Instantiate the filter and set it's success manager like so

JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager()),FilterSecurityInterceptor.class);

jwtAuthenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());

Now use the above variable to set the filter. This is a great reference project: https://gitlab.com/palmapps/jwt-spring-security-demo/-/tree/master/.

I solved this problem with another approach. In the JwtAuthenticationFilter class we need to set authentication object in context and call chain.doFilter. Calling super.successfulAuthentication can be skipped as we have overridden the implementation.

 @Override
   protected void successfulAuthentication(HttpServletRequest request,

   HttpServletResponse response, FilterChain chain, Authentication authResult)
                throws IOException, ServletException {
            //super.successfulAuthentication(request, response, chain, authResult);
            SecurityContextHolder.getContext().setAuthentication(authResult);
            chain.doFilter(request, response);
        }

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/**");
        this.setAuthenticationManager(authenticationManager);
        //this.setAuthenticationSuccessHandler(new JwtAuthenticationSuccessHandler());
    }
Related