How to read a JSON request inside a Spring Boot filter?

Let’s say you are using a filter in your spring boot application.

Spring boot (Spring Security) already internally uses many filters to filter requests coming to your application.

If you are going to create a custom filter you can do so by implementing Filter interface from javax servlet package or by extending GenericFilterBean/ OncePerRequestFilter provided by Spring.

Let’s take the former route.

Here is a filter created in a sample boot application:

@Component
public class TestFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {

		//YOUR LOGIC HERE

		chain.doFilter(request, response);
		return;

	}

}

You do three things here:

  • Implement Filter interface
  • Override doFilter method
  • Pass the request to the next filter using chain.doFilter() method call.

The above filter does nothing, it just forwards the request to the next filter in the filter chain.

But what if you want to read the request and do some processing on it.

You may try something like this:

@Component
public class TestFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest httpReq = (HttpServletRequest) request;

		byte[] body = StreamUtils.copyToByteArray(request.getInputStream());

		Map<String, Object> jsonRequest = new ObjectMapper().readValue(body, Map.class);

		System.out.println(jsonRequest);

		chain.doFilter(request, response);
		return;

	}

}

The above code won’t work!

This is because you can read a http servlet request only once and if you read it in your filter , the other filters in Spring Security Filter Chain can’t read them and so you get an exception:

So how to resolve this?

You create a wrapper around the http servlet request object and do your processing on it.

You need to create two wrappers actually.

One for the HttpServletRequest object

Another for the ServletInputStream object.

Here are the two wrapper implementations:

HttpServletRequestWrapper:

package com.example.filter;

import java.io.IOException;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.util.StreamUtils;

public class RequestWrapper extends HttpServletRequestWrapper {

	private byte[] body;

	public RequestWrapper(HttpServletRequest request) throws IOException {
		super(request);

		this.body = StreamUtils.copyToByteArray(request.getInputStream());
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		return new ServletInputStreamWrapper(this.body);

	}

}

You create the wrapper by extending HttpServletRequestWrapper .

You implement a constructor inside the wrapper where you get the actual httpservlet request object and store its contents in a byte array.

You also override the method getInputStream() and instead of returning the input stream from the original http servlet request object you return it from another wrapper you are going to create (ServletInputStreamWrapper).

Advertisements

Here is that wrapper:

package com.example.filter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;

public class ServletInputStreamWrapper extends ServletInputStream{

	
	private InputStream inputStream;
	
	
	public  ServletInputStreamWrapper(byte[] body) {
		
		
		this.inputStream = new ByteArrayInputStream(body);
	}
	
	@Override
	public boolean isFinished() {
		
		try {
			
			return inputStream.available() == 0;
			
		}catch(Exception e) {
			
			return false;
		}
	}

	@Override
	public boolean isReady() {
		return true;
	}

	@Override
	public void setReadListener(ReadListener listener) {

		
	}

	@Override
	public int read() throws IOException {
		return this.inputStream.read();
	}

}

You need to extend ServletInputStream class to create this wrapper.

Create a constructor inside this wrapper where you get the byte array populated in the first wrapper ( HttpServletRequestWrapper) and convert it into an input stream . It is this stream which you are going to use to read the input request.

And then you override the following methods:

  • isReady(0
  • isFinished()
  • read()
  • setReadListener() //this can be left empty

isReady() can always return true

isFinished() just checks if there is any data in the inputStream

read() reads from the input stream

Once the wrappers are created , you can read your json request inside your Filter using the below code:

package com.example.filter;

import java.io.IOException;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class TestFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		System.out.println("Inside Filter");
		

//Wrap the request 
		RequestWrapper wrapper = new RequestWrapper((HttpServletRequest) request);

//Get the input stream from the wrapper and convert it into byte array
		byte[] body = StreamUtils.copyToByteArray(wrapper.getInputStream());

// use jackson ObjectMapper to convert the byte array to Map (represents JSON)
		Map<String, Object> jsonRequest = new ObjectMapper().readValue(body, Map.class);

		System.out.println(jsonRequest);

		chain.doFilter(wrapper, response);
		return;

	}

}

Now let’s test this

I created a simple REST API :

package com.example.filter;

import java.util.Map;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@PostMapping("/test")
	public Map<String, Object> test(@RequestBody Map<String, Object> request) {

		request.put("success", "true");

		return request;
	}
}

Let’s run the application and see if the input passed to the above API can be read through the filter:

The json request got read inside the filter!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s