springspring-bootspring-securityspring-security-rest

Spring Security Custom Filter gets called Multiple times


I have a Custom Spring Security filter which is getting called Multiple times and I am not able to figure out why. I searched it and tried adding FilterRegistrationBean as some post suggested but still my Secuirty filter gets called multiple times and throws below error -

20:57:49.975 [http-nio-8888-exec-2] DEBUG c.s.m.security.RESTSecurityFilter - Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fb70fff6: Principal: srib; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: BC1D5AB21EE4586F2A76C7901F1F953F; Granted Authorities: ROLE_USER, ROLE_ADMIN
20:57:50.220 [http-nio-8888-exec-2] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - 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
    at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:472)
    at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:129)
    at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:129)
    at org.springframework.security.web.util.OnCommittedResponseWrapper.sendError(OnCommittedResponseWrapper.java:115)
    at javax.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:129)
    at org.springframework.security.web.util.OnCommittedResponseWrapper.sendError(OnCommittedResponseWrapper.java:115)
    at org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:488)
    at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:53)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at xxxxxxxxxxxxxxxxxxxxxx.RESTSecurityFilter.successfulAuthentication(RESTSecurityFilter.java:83)

Below are my config and filter -

RESTSecurityConfig

@Configuration
@EnableWebSecurity
@Order(1)
public class RESTSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RESTSecurityFilter authenticationFilter() throws Exception {
        RESTSecurityFilter authenticationFilter = new RESTSecurityFilter("/");
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationFilter;
    }

    @Bean
    public RESTAuthenticationProvider authenticationProvider() {            
        return new RESTAuthenticationProvider();
    }

    @Bean
    public FilterRegistrationBean registration() {
        RESTSecurityFilter authenticationFilter = new RESTSecurityFilter("/");
        FilterRegistrationBean registration = new FilterRegistrationBean(authenticationFilter);
        registration.setEnabled(false);
        return registration;
    }

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests().anyRequest().authenticated().and().addFilterAfter(authenticationFilter(),
                UsernamePasswordAuthenticationFilter.class);
    }
}

RESTSecurityFilter

public class RESTSecurityFilter extends AbstractAuthenticationProcessingFilter {    
    private static final Logger log = LoggerFactory.getLogger(RESTSecurityFilter.class);    
    private static final String ACCESS_KEY_PARAMETER_NAME = "x-access-key";    
    private static final String SIGNATURE_PARAMETER_NAME = "x-signature";    
    private static final String NONCE_PARAMETER_NAME = "x-nonce";    
    private static final String TIMESTAMP_PARAMETER_NAME = "x-timestamp";    
    private static final String SECRET_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";

    protected RESTSecurityFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        String accessKey = getHeaderValue(request, ACCESS_KEY_PARAMETER_NAME);
        String signature = getHeaderValue(request, SIGNATURE_PARAMETER_NAME);
        String nonce = getHeaderValue(request, NONCE_PARAMETER_NAME);
        String timestamp = getHeaderValue(request, TIMESTAMP_PARAMETER_NAME);

        String message = accessKey + ":" + nonce + ":" + timestamp;
        String hashSignature = null;
        try {

            hashSignature = HMacUtility.calculateHmac(message, SECRET_KEY);
            log.info("hashSignature : {}", hashSignature);

        }
        catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        AbstractAuthenticationToken authRequest = createAuthenticationToken(accessKey,
                new RESTCredentials(hashSignature, signature));

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.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);
        // return;      // <--- If uncommented, error goes away but response gets stuck in infinite loop
    }

    private String getHeaderValue(HttpServletRequest request, String headerParameterName) {
        return (request.getHeader(headerParameterName) != null) ? request.getHeader(headerParameterName) : "";
    }

    private AbstractAuthenticationToken createAuthenticationToken(String apiKeyValue, RESTCredentials restCredentials) {
        return new RESTAuthenticationToken(apiKeyValue, restCredentials);
    }

    protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

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

Solution

  • It's because Spring Boot picks up any Filter exposed as a @Bean and adds it to the ServletContext. By exposing it as a @Bean and by adding it to the Spring Security Filter Chain, you've effectively registered it twice.

    Likely, you can simply remove the @Bean annotation from your authenticationFilter() method.