×
☰ See All Chapters

Implementing Filters in Spring Security

In Spring Security, following successful authentication, specific filters handle authorization, forming a chain of responsibilities. These HTTP filters meticulously manage each aspect necessary for processing requests, executing logic, and passing requests to subsequent filters. With Spring Security, customization of filters within the filter chain is not only possible but encouraged. Custom filters can augment authentication processes, such as additional verification steps like email checks or one-time passwords. Additionally, they enable functionalities like auditing authentication events, crucial for debugging and understanding user behaviour. Proficiency in customizing the HTTP filter chain is a valuable skill, empowering developers to implement diverse authentication strategies and integrate with external systems for enhanced security and auditability.

You can customize the filter chain by adding new filters before, after, or at the position of existing ones. This allows you to tailor authentication as well as the entire process applied to requests and responses.

Steps to Add Filters

Step 1: Implement the Filter

  • Spring Security filters are standard HTTP filters, implemented by implementing the Filter interface from the javax.servlet package. 

  • The core logic of the filter is implemented within the doFilter() method, which receives the ServletRequest, ServletResponse, and FilterChain as parameters. 

public class RequestValidationFilter implements Filter {
   
@Override
   
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
           
throws ServletException, IOException {
       
// Logic to validate the request, e.g., checking for the presence of a trace ID header
   
}
}

 

Step 2: Add the Filter to the Filter Chain:

  • Configuration of the filter occurs within the application's configuration class, typically by extending WebSecurityConfigurerAdapter and overriding the configure(HttpSecurity http) method. 

  • The filter chain represents a sequence of filters applied to HTTP requests in a defined order, forming the backbone of Spring Security's security mechanisms. 

  • Custom filters can be added at different points in the filter chain: before, after, or at the position of existing filters. 

@Configuration
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

   
@Override
   
protected void configure(HttpSecurity http) throws Exception {
       http.addFilterBefore(
new RequestValidationFilter(), BasicAuthenticationFilter.class)
               .authorizeRequests()
               .anyRequest().permitAll();
   }
}

 

Spring Security offers various filter implementations and their predefined order for ease of use. Among these filters are:

  1. BasicAuthenticationFilter: Responsible for handling HTTP Basic authentication, when applicable. 

  2. CsrfFilter: Manages protection against cross-site request forgery (CSRF) attacks. 

  3. CorsFilter: Deals with authorization rules for cross-origin resource sharing (CORS). 

In previous chapters, we learned that invoking the httpBasic() method of the HttpSecurity class integrates HTTP Basic authentication. Consequently, an instance of BasicAuthenticationFilter is automatically added to the filter chain.

Each filter in the chain is assigned an order number, dictating the sequence of their execution. Custom filters can be seamlessly added alongside Spring Security's provided filters, either before, after, or at the position of an existing filter. Each position corresponds to an index, often referred to as "the order."

It's important to note that multiple filters may share the same order value within the chain. In such cases, Spring Security does not guarantee the order in which they are invoked, emphasizing the need for strategic placement of filters to achieve the desired security behaviour.

Methods for Adding Filters

Three main methods facilitate this customization: addFilterBefore, addFilterAfter, and addFilterAt. Let's delve into each method along with examples.

addFilterBefore

This method adds a custom filter before a specified filter in the chain. It receives two parameters:

  • An instance of the custom filter to be added. 

  • The type of filter before which the custom filter should execute. 

Example

We create a request validation filter and place it before the authentication process. This filter verifies the presence of the 'traceId' header. If the header is present in the request, the application proceeds to authenticate the request. If the header is absent, the application responds with an HTTP status of 400 Bad Request and returns this status to the client.

import org.springframework.http.HttpStatus;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class RequestValidationFilter implements Filter {
   
@Override
   
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
           
throws ServletException, IOException {
       
var httpRequest = (HttpServletRequest) request;
       
var httpResponse = (HttpServletResponse) response;
       
String traceId = httpRequest.getHeader("traceId");
       
if (traceId == null || traceId.isBlank()) {
           
httpResponse.setStatus(HttpStatus.BAD_REQUEST.value());
           
return;
       }
       filterChain.doFilter(request, response);
   }
}

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

   
@Override
   
protected void configure(HttpSecurity http) throws Exception {
       http.httpBasic();
       http.addFilterBefore(
new RequestValidationFilter(), BasicAuthenticationFilter.class)
               .authorizeRequests()
               .anyRequest().permitAll();
   }

}

 

addFilterAfter

This method inserts a custom filter after a specified filter in the chain. It also requires two parameters:

  • An instance of the custom filter to be inserted. 

  • The type of filter after which the custom filter should execute. 

Example

We add a filter named 'AuditingFilter' after the 'BasicAuthenticationFilter' in the filter chain. This filter logs the requests that the application authenticates.

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class AuditingFilter implements Filter {
   
@Override
   
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
           
throws IOException, ServletException {
       
var httpRequest = (HttpServletRequest) request;
       log.info(
"HTTP Method " + httpRequest.getMethod());
       log.info(
"Path " + httpRequest.getRequestURI());
       log.info(
"Headers " + httpRequest.getHeaderNames());
       log.info(
"Successfully authenticated request with traceId " + httpRequest.getHeader("traceId"));
       filterChain.doFilter(request, response);
   }
}

 

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

   
@Override
   
protected void configure(HttpSecurity http) throws Exception {
       http.httpBasic();
       http.addFilterAfter(
new AuditingFilter(), BasicAuthenticationFilter.class)
               .authorizeRequests()
               .anyRequest().permitAll();
   }

}

 

addFilterAt

This filter is particularly useful when implementing a custom authentication approach to replace or augment the default behavior assumed by Spring Security filters. An example scenario could involve implementing authentication methods such as:

  • Authenticating based on a static header value 

  • Using a symmetric key for request authentication 

  • Employing a one-time password (OTP) mechanism 

It's important to note that when adding a filter at a specific position, Spring Security does not automatically assume it's the only filter at that position. Additional filters can be added at the same location within the chain, leading to uncertainty regarding the order of execution. Contrary to a common misconception, adding a filter at a known position does not replace existing filters. Therefore, it's crucial to avoid adding unnecessary filters to the chain. For instance, if a custom authentication filter is added, it's advisable to remove the BasicAuthenticationFilter from the chain. To exclude the BasicAuthenticationFilter, refrain from invoking the httpBasic() method in the HttpSecurity class configuration.

Example

Suppose we create an authentication filter responsible for verifying the validity of a token passed in the Authorization header. This filter compares the received token with the one stored in a secure location, such as a secret vault or database. If the token matches, indicating successful authentication, the filter allows the request to proceed to the next component in the filter chain. However, if the tokens do not match, indicating authentication failure, the filter sets the HTTP status code to 401 Unauthorized and halts further processing of the request in the filter chain.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class StaticTokenAuthenticationFilter implements Filter {
   
@Value("${authorization.token}")
   
private String authorizationToken;

   
@Override
   
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
           
throws IOException, ServletException {
       
var httpRequest = (HttpServletRequest) request;
       
var httpResponse = (HttpServletResponse) response;
       
String authorizationHeader = httpRequest.getHeader("Authorization");
       
if (this.authorizationToken.equals(authorizationHeader)) {
           filterChain.doFilter(request, response);
       }
else {
           httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
       }
   }
}

 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
public class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

   
@Autowired
   
private StaticTokenAuthenticationFilter staticTokenAuthenticationFilter;

   
@Override
   
protected void configure(HttpSecurity http) throws Exception {
       http.addFilterAt(
staticTokenAuthenticationFilter,
                       
BasicAuthenticationFilter.class)
               .authorizeRequests()
               .anyRequest().permitAll();
   }

}

 

 


All Chapters
Author