[go: up one dir, main page]

DEV Community

Cover image for JAVA SpringBoot request filters
Ahmed Ashraf
Ahmed Ashraf

Posted on • Originally published at ahmedash.com

JAVA SpringBoot request filters

Intro

Filter provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, you want to inject a custom header to a request/response based on some conditions, or you want to run some checks before accessing the controller and serve the request.

Bad

Sometimes we see people implement those kinds of checks and assertions in the controller. yes, it works but it's not the best place to put it there.

Good

Use Filters which it runs before accessing the controllers and serve your requests.

Filters flow

filters-flow

Hands On

How to write?

In order to create a request filter. you just need to create a class that implements the Filter interface.

package com.ahmedash95.example.filters;

import javax.servlet.*;

@Component
public class RoomsCreateFilter implements Filter 
{
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
     // your code goes here.

    // then you need to pass it in the chain. 
    chain.doFilter(request, response);
  }
}
Enter fullscreen mode Exit fullscreen mode

If you are not familiar with the code above. here are some clarifications:

  • @Component is an annotation that allows us to register the class as a bean in the ApplicationContext
  • implements Filter interface tells Spring how this class should be used when register via @Component
  • import javax.servlet.*; imports Filter, ServletRequest, ServletResponse, FilterChain, and ServletException.
  • chain.doFilter(request, response); at the end is a must. as it tells spring how to continue handling the request. without it the response will be empty as the chain got broken.

Return response on errors

If you validate something and want to stop processing the request and just return the response. you can simply modify the Response object and return it from your Filter class.

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    boolean valid = false; // check for something
    if (!valid) {
        ((HttpServletResponse) response).setStatus(422);
        response.getOutputStream().write("Validation error".getBytes());
        return;
    }


    chain.doFilter(request, response);
}
Enter fullscreen mode Exit fullscreen mode

Examples

Let's write some real-world examples and some different scenarios to make sure you get the full picture.

1.Ensure API key is provided and valid

Let's say you are working on API that provides something. and you want to ensure that all requests to your application will have the API key and the key is valid. of course, you don't want to do the key validation to be in every controller and method.

Let's start by creating our filter first

package com.example.demo;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class APIKeyValidatorFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String key = req.getHeader("X-API-KEY");
        if(key == null) {
            ((HttpServletResponse) response).setStatus(401);
            response.getOutputStream().write("API Key is missing!".getBytes());
            return;
        }

        if(!KeyValidator.valid(key)) {
            ((HttpServletResponse) response).setStatus(403);
            response.getOutputStream().write("API Key is invalid".getBytes());
            return;
        }

        chain.doFilter(request, response);
    }
}
Enter fullscreen mode Exit fullscreen mode

As you see. we basically do some checks. if the key is not provided we show the key missing error message. if is provided but not valid we show The key is an invalid message.

2.Ratelimit some endpoints

Let's say you want to protect the POST /comment endpoint. so your users must not be able to submit more than 2 comments in one minute. Again it can be done in the controller but It's not the best place to do it.

Let's create the Filter class

package com.example.demo;

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

public class PostCommentRateLimit implements Filter
{
    RateLimiter rateLimiter;

    public PostCommentRateLimit(RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        int userId = 1; // Get User
        boolean valid = rateLimiter.validate(String.format("ratelimit.user.comments:%d", userId), 2);
        if(!valid) {
            ((HttpServletResponse) response).setStatus(429);
            response.getOutputStream().write("Too Many Requests".getBytes());
            return;
        }


        chain.doFilter(request, response);
    }
}
Enter fullscreen mode Exit fullscreen mode

Few things to explain here:

  • We did not use @Component here. because we want to apply the filter on POST /comment endpoint only. so we will register it ourself next.
  • We have RateLimiter in constructor. and you don't need to worry about it. use whatever library fits your needs.

As mentioned we did not use @Component because it would apply the filter for all requests. instead we will create another class to register our filters the way we want.

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RequestsFilterRegister {

    @Autowired
    RateLimiter rateLimiter;

    @Bean
    public FilterRegistrationBean<PostCommentRateLimit> registerPostCommentsRateLimiter(){
        FilterRegistrationBean<PostCommentRateLimit> registrationBean  = new FilterRegistrationBean<>();

        registrationBean.setFilter(new PostCommentRateLimit(rateLimiter));
        registrationBean.addUrlPatterns("/comment");

        return registrationBean;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • We created RequestsFilterRegister as @Configuration class
  • The only method there registerPostCommentsRateLimiter to register the filter as we want.
  • we used addUrlPatterns to apply the filter only on /comment endpoint.

Now we have one small problem. the filter is applied to any method on /comment either GET or POST. and to fix that we just need to modify the PostCommentRateLimit@doFilter to skip if the method is not post

HttpServletRequest req = (HttpServletRequest) request;
if (!req.getMethod().equals("POST")) {
    chain.doFilter(request, response);
    return;
}
Enter fullscreen mode Exit fullscreen mode

Now the full class is

package com.example.demo;

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

public class PostCommentRateLimit implements Filter
{
    RateLimiter rateLimiter;

    public PostCommentRateLimit(RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (!req.getMethod().equals("POST")) {
            chain.doFilter(request, response);
            return;
        }

        int userId = 1; // Get User
        boolean valid = rateLimiter.validate(String.format("ratelimit.user.comments:%d", userId), 2);
        if(!valid) {
            ((HttpServletResponse) response).setStatus(429);
            response.getOutputStream().write("Too Many Requests".getBytes());
            return;
        }


        chain.doFilter(request, response);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclution

Now you know What is Request Filters, how to write them, and you applied some real-world examples. also you saw how to customize the routes and methods to apply filters on.

The full source code https://github.com/ahmedash95/java-spring-request-filters

For more, you can check baeldung.com/spring-boot-add-filter

Note

I'm still learning Java and spring. so if you see any mistakes or a better way in writing Filters Your comments and suggestions will be greatly appreciated.

Top comments (2)

Collapse
 
sgenlecroyant profile image
sgenlecroyant

Thanks @ahmedashrafelbendary98 , it helped me a lot!

Collapse
 
ramziba profile image
Ramzi

After one year of writing this article, it is still useful and it helped me a lot.
Thank you Ahmed!