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