Spring Boot 2: how to customize Servlet Filter

Posted by lokie538 on Mon, 30 Dec 2019 10:52:03 +0100

1. Preface

Sometimes we need to declare some custom servlet filters in the Spring Boot Servlet Web application to handle some logic. For example, simple permission system, request header filtering, XSS attack prevention, etc. This article explains how to declare custom servlet filters in Spring Boot applications and define their respective scopes and orders.

2. User defined Filter

Some people may say that declaring Servlet Filter is not to implement the Filter interface. There is nothing to talk about! Yes, that's right, but many times we don't want our declared Filter to work on all requests. Even when a request goes through multiple filters, it needs to be executed in the given order. Next, I will explain how to realize the above functions one by one.

2.1 declaration of filter

In Spring Boot, you only need to declare a Spring Bean that implements the javax.servlet.Filter interface. As follows:

@Configuration
public class FilterConfig {


    @Bean
    public Filter requestFilter() {
        return (request, response, chain) -> {
            //todo your business
        };
    }

    @Bean
    public Filter responseFilter() {
        return (request, response, chain) -> {
            //todo your business
        };
    }

}

It's very simple, isn't it? But this way can't guarantee the order, and it works on all requests, that is, the Ant rule intercepted is / *. So we need to improve

2.2 implement Filter sequencing

If you need to implement sequencing, you can use the @ Order annotation provided by Spring or the Ordered interface. There is a pit here: if @ Order annotation is used, it must be annotated to specific classes. To facilitate JavaConfig style declarations. We can implement the OrderedFilter interface, which is the combination of the Filter interface and the Ordered interface. The final configuration is as follows

@Configuration
public class FilterConfig {

    @Bean
    public OrderedFilter responseFilter() {
        return new ResponseFilter("/foo/*");
    }

    @Bean
    public OrderedFilter requestFilter() {
        return new RequestFilter("/foo/*");

    }

}

The rule that Filter executes is that the smaller the number, the earlier it executes. The priority of Bean instantiation is the same as before.

2.3 user defined Filter scope

After the implementation of sequencing, let's see how to implement the scope of custom Filter. Let's start with the idea:

Get the URI of the request through the ServletRequest object, and then match the URI with the ANT style. For the ANT style, please refer to my article Article. Match by executing specific logic, otherwise skip the Filter.

This is very suitable for abstracting a base class to fix the process, leaving an abstract method as a function hook, just inheriting the base class to implement the abstract method hook. To ensure the sequential execution of the base class, we still implement the OrderedFilter interface. Let's define the base class:

package cn.felord.springboot.filters;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;

/**
 * The type Abstract filter bean.
 *
 * @author Felordcn
 * @since 11 :19
 */
public abstract class AbstractFilterBean implements OrderedFilter {
    private Set<String> urlPatterns = new LinkedHashSet<>();

    public AbstractFilterBean(String... urlPatterns) {
        Collections.addAll(this.urlPatterns, urlPatterns);
    }

    /**
     * Function hooks of respective logic
     *
     * @param request  the request
     * @param response the response
     */
    public abstract void internalHandler(ServletRequest request, ServletResponse response);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         // Perform ant match true to perform specific interception logic false skip
        if (this.antMatch(request)) {
            this.internalHandler(request, response);
        }
        chain.doFilter(request, response);
    }


    private boolean antMatch(ServletRequest request) {
        Set<String> urlPatterns = getUrlPatterns();

        if (!CollectionUtils.isEmpty(urlPatterns)) {
            //Ant matching
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String uri = httpServletRequest.getRequestURI();
            Optional<String> any = urlPatterns.stream().filter(s -> {
                AntPathMatcher antPathMatcher = new AntPathMatcher();
                return antPathMatcher.match(s, uri);
            }).findAny();

            return any.isPresent();
        }
        // If there is no element to indicate all matches
        return true;
    }


    public Set<String> getUrlPatterns() {
        return urlPatterns;
    }
}

Let's implement a specific Filter logic to print the URI of the request:

@Slf4j
public class RequestFilter extends AbstractFilterBean {

    public RequestFilter(String... urlPatterns) {
        super(urlPatterns);
    }

    @Override
    public void internalHandler(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        log.info("request from {}", httpServletRequest.getRequestURI());
    }

    @Override
    public int getOrder() {
       // Define your own priorities
        return 0;
    }    
}

Then define its urlPatterns and register them in the Spring IoC container. If there are multiple and you want to execute them in a certain order, follow the method provided in Chapter 2.2.

3. Mechanism of spring boot

These are our own wheels. In fact, Spring Boot also provides a Filter registration mechanism to implement sequential execution and declaration scope. Our above logic can be changed to:

package cn.felord.springboot.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Using the registration mechanism provided by Spring Boot
 *
 * @author Felordcn
 * @since 14:27
 **/
@Configuration
@Slf4j
public class SpringFilterRegistrationConfig {


    @Bean
    public FilterRegistrationBean<Filter> responseFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setName("responseFilter");
        registrationBean.setOrder(2);
        registrationBean.setFilter((request, response, chain) -> {
            HttpServletResponse servletResponse = (HttpServletResponse) response;
            log.info("response status {}", servletResponse.getStatus());
            chain.doFilter(request,response);
        });
        registrationBean.addUrlPatterns("/foo/*");
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<Filter> requestFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setName("requestFilter");
        registrationBean.setOrder(1);
        registrationBean.setFilter((request, response, chain) -> {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            log.info("request from {}", httpServletRequest.getRequestURI());
            chain.doFilter(request,response);
        });
        registrationBean.addUrlPatterns("/foo/*");
        return registrationBean;
    }

}

3.1 points

  • There is a one-to-one relationship between FilterRegistrationBean and Filter.
  • If there are more than one FilterRegistrationBean, it needs to call its setName(String name) to declare a unique name for it, otherwise only the first successful registration is valid.
  • If you need to ensure the call order, you can set it by calling its setOrder(int order) method.

4. summary

In this article, we implement the use of custom Filter through two ways: Custom and Spring Boot. Although Spring Boot is more convenient, the custom way can better reflect your object-oriented understanding and improve your abstract ability. Hope to pay more attention, as usual. By focusing on the public address: Felordcn reply to f01, get this article DEMO.

Pay attention to the public address: Felordcn for more information

Personal blog: https://felord.cn

Topics: Programming Spring Java SpringBoot Lombok