Spring Boot Filters Example

Introduction

Servlet filters are Java classes that are invoked at pre and post processing of requests. So filters are used

  • to intercept requests from clients before they access requests at backend (server).
  • to manipulate responses from server before they are sent back to the client

RequestContextFilter is also a servlet filter that extends OncePerRequestFilter that extends GenericFilterBean which implements servlet Filter. The details for each filter type could be found in the link itself.

Read more

Examples that have been identified for Filter design are:

  • Authentication Filters
  • Logging and Auditing Filters
  • Image conversion Filters
  • Data compression Filters
  • Encryption Filters
  • Tokenizing Filters
  • Filters that trigger resource access events
  • XSL/T filters
  • Mime-type chain Filter

In this example I am going to manipulate the response headers to control cache for the browser and to set Content Security Policy in the HTTP header. I am going to use Spring framework’s RequestContextFilter instead of Filter.

Prerequisites

Java 1.8+, Spring Boot 2.6.7, Maven 3.8.5

Request Context Filter Example

I am creating two separate classes that will extend RequestContextFilter. I could have modify the HTTP response header in the single filter class but I also want to show you how to register multiple filters to FilterRegistrationBean in spring boot application.

CSP (Content-Security-Policy) Filter

The CSP filter is used to restrict the usage of script (JavaScript), CSS files from particular sources. I am only allowing the same domain should load these script files.

public class CspControlFilter extends RequestContextFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		System.out.println("CSP Filter...");

		ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
		ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

		filterChain.doFilter(requestWrapper, responseWrapper);

		setCacheHeaders(responseWrapper);

		responseWrapper.copyBodyToResponse();
	}

	private void setCacheHeaders(ContentCachingResponseWrapper responseWrapper) {
		responseWrapper.setHeader("Content-Security-Policy", "default-src 'self'");
		responseWrapper.setHeader("Content-Security-Policy", "script-src 'self'");
	}

}

To find more about content security policy please have a look at the tutorial Content Security Policy in Web Applications.

Cache Control Filter

The following class modifies the response header to control the caching mechanism in browser or client.

The Cache-Control HTTP header field holds directives (instructions) — in both requests and responses — that control caching in browsers and shared caches (e.g. Proxies, CDNs).

no-cache

The no-cache response directive indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse, even when the cache is disconnected from the origin server.

must-revalidate

The must-revalidate response directive indicates that the response can be stored in caches and can be reused while fresh. If the response becomes stale, it must be validated with the origin server before reuse.

no-store

The no-store response directive indicates that any caches of any kind (private or shared) should not store this response.

Pragma

Pragma is a legacy of HTTP/1.0 and hasn’t been needed after Internet Explorer 5, or Netscape 4.7.

Pragma is the HTTP/1.0 implementation and cache-control is the HTTP/1.1 implementation of the same concept. They both are meant to prevent the client from caching the response. Older clients may not support HTTP/1.1 which is why Pragma header is still in use.

Expires 0

Expires: 0 means that a cache will always treat this entry as stale i.e. it needs to revalidate first before returning it to a client.

Here is an example of RequestContextFilter that controls the cache in browser:

public class CacheControlFilter extends RequestContextFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		System.out.println("Cache Filter...");

		ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
		ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

		filterChain.doFilter(requestWrapper, responseWrapper);

		setCacheHeaders(responseWrapper);

		responseWrapper.copyBodyToResponse();
	}

	private void setCacheHeaders(ContentCachingResponseWrapper responseWrapper) {
		responseWrapper.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
		responseWrapper.setHeader("Pragma", "no-cache"); // HTTP 1.0
		responseWrapper.setHeader("Expires", "0");
	}

}

You can add more header values as you may require for your application.

Filter Registration Bean

FilterRegistrationBean is a ServletContextInitializer to register Filters in a Servlet 3.0+ container. Similar to the registration features provided by ServletContext but with a Spring Bean friendly design.

The Filter must be specified before calling RegistrationBean.onStartup(ServletContext). Registrations can be associated with URL patterns and/or servlets (either by name or via a ServletRegistrationBeans). When no URL pattern or servlets are specified the filter will be associated to /*. The filter name will be deduced if not specified.

Here I am going to register above two Request Context Filters with the URL pattern /*.

@Configuration
public class FilterConfig {

	@Bean
	public FilterRegistrationBean<Filter> cacheControlFilter() {
		CacheControlFilter cacheControlFilter = new CacheControlFilter();

		return createFilterRegistration(cacheControlFilter);
	}

	@Bean
	public FilterRegistrationBean<Filter> cspControlFilter() {
		CspControlFilter cspControlFilter = new CspControlFilter();

		return createFilterRegistration(cspControlFilter);
	}

	private FilterRegistrationBean<Filter> createFilterRegistration(Filter filter) {
		FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>(filter);

		List<String> urlPatterns = new ArrayList<>();
		urlPatterns.add("/*");

		filterRegistrationBean.setUrlPatterns(urlPatterns);

		return filterRegistrationBean;
	}

}

REST Controller

A simple REST controller class that publishes a simple endpoint to test the application.

@RestController
public class AppRestController {

	@GetMapping("/")
	public ResponseEntity<String> hello() {
		return new ResponseEntity<String>("Hello", HttpStatus.OK);
	}

}

Spring Boot Main Class

A class having a main method with @SpringBootApplication annotation will deploy the application in the embedded Tomcat server.

@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

}

Testing Request Context Filter

Once you run the main class and access the URL http://localhost:8080 in the browser, you will see the following output in the console:

Cache Filter...
CSP Filter...
Cache Filter...
CSP Filter...
Cache Filter...
CSP Filter...

Therefore the filters are called for requests and responses.

Source Code

Download

Leave a Reply

Your email address will not be published.