Spring Boot 2 实战: 自定义 Servlet Filter 的两种方式

2021-03-30 18:28

阅读:692

标签:config   let   配置   注册   pre   pattern   面向对象   资料   作用域   

1.前言

有些时候我们需要在 Spring Boot Servlet Web 应用中声明一些自定义的 Servlet Filter来处理一些逻辑。比如简单的权限系统、请求头过滤、防止 XSS ***等。本篇将讲解如何在 Spring Boot 应用中声明自定义 Servlet Filter 以及定义它们各自的作用域和顺序。

2. 自定义 Filter

可能有人说声明 Servlet Filter 不就是实现 Filter 接口嘛,没有什么好讲的!是的这个没错,但是很多时候我们并不想我们声明的 Filter 作用于全部的请求。甚至当一个请求经过多个 Filter 时需要按照既定的顺序执行。接下来我会一一讲解如何实现以上的功能。

2.1 Filter 的声明

在 Spring Boot 中 只需要声明一个实现 javax.servlet.Filter 接口的 Spring Bean 就可以了。如下:

@Configurationpublic class FilterConfig {

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

``

非常简单不是吗?但是这种方式无法保证顺序,而且作用于所有的请求,即拦截的 Ant 规则为 /*。所以需要我们改进

2.2 实现 Filter 顺序化

如果需要实现顺序化,可以借助于 Spring 提供的 @Order 注解或者 Ordered 接口。这里有一个坑:如果使用 @Order 注解一定要注解标注到具体的类上。为了方便 JavaConfig 风格的声明。我们可以实现 OrderedFilter 接口,该接口是 Filter 接口和 Ordered接口的复合体,最终上面的配置如下:

@Configurationpublic class FilterConfig {
    @Bean    public OrderedFilter responseFilter() {        return new ResponseFilter("/foo/*");    }
    @Bean    public OrderedFilter requestFilter() {        return new RequestFilter("/foo/*");
    }
}

Filter 执行的规则是 数字越小越先执行 。跟之前 Bean 实例化的优先级是一致的。

2.3 自定义 Filter 作用域

实现了顺序化之后我们来看看如何实现自定义 Filter 的作用域。我们先说一下思路:

通过 ServletRequest 对象来获取请求的 URI,然后对 URI 进行 ANT 风格匹配,关于 ANT 风格可以参考我的这一篇文章[1]。匹配通过执行具体的逻辑,否则跳过该 Filter

这里非常适合抽象一个基类来把该流程固定下来,留一个抽象方法作为函数钩子,只需要继承基类实现该抽象方法钩子就可以了。为了保证顺序执行基类我们依然实现了 OrderedFilter 接口,我们来定义基类:

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 urlPatterns = new LinkedHashSet();
    public AbstractFilterBean(String... urlPatterns) {        Collections.addAll(this.urlPatterns, urlPatterns);    }
    /**     * 各自逻辑的函数钩子     *     * @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 {         // 进行ant匹配  true 执行具体的拦截逻辑 false  跳过        if (this.antMatch(request)) {            this.internalHandler(request, response);        }        chain.doFilter(request, response);    }

    private boolean antMatch(ServletRequest request) {        Set urlPatterns = getUrlPatterns();
        if (!CollectionUtils.isEmpty(urlPatterns)) {            //进行Ant匹配处理            HttpServletRequest httpServletRequest = (HttpServletRequest) request;            String uri = httpServletRequest.getRequestURI();            Optional any = urlPatterns.stream().filter(s -> {                AntPathMatcher antPathMatcher = new AntPathMatcher();                return antPathMatcher.match(s, uri);            }).findAny();
            return any.isPresent();        }        // 如果 没有元素 表示全部匹配        return true;    }

    public Set getUrlPatterns() {        return urlPatterns;    }}

``

我们来实现一个具体的 Filter 逻辑,打印请求的 URI

@Slf4jpublic 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() {       // 定义自己的优先级        return 0;    }}

``

然后定义好其 urlPatterns 并将其注册到 Spring IoC 容器中就行了,如果有多个而且希望按照一定的顺序执行,遵循 2.2 章节 提供的方法就可以了。

3. Spring Boot 的机制

以上方式是我们自己造的轮子。其实 Spring Boot 还提供了 Filter 注册机制来实现顺序执行和声明作用域。我们上面的逻辑可以改为:

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;
/** * 使用 Spring Boot 提供的注册机制 * * @author Felordcn * @since 14:27 **/@Configuration@Slf4jpublic class SpringFilterRegistrationConfig {

    @Bean    public FilterRegistrationBean responseFilter() {        FilterRegistrationBean 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 requestFilter() {        FilterRegistrationBean 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 要点

  • FilterRegistrationBeanFilter 之间是一对一关系。
  • 如果存在多个 FilterRegistrationBean 需要调用其 setName(String name) 为其声明唯一名称,否则只有第一个注册成功的有效。
  • 如果需要保证调用顺序可通过调用其 setOrder(int order) 方法进行设置。

4. 总结

我们在本文中通过自定义和 Spring Boot 提供的两种方式实现了使用自定义 Filter ,虽然 Spring Boot 提供的方式更加方便一些,但是自定义的方式更能体现你对面向对象理解和提高你的抽象能力。希望多多关注,与往常一样。通过关注公众号:Felordcn 回复 f01获取本文 DEMO

参考资料

[1]文章: https://www.felord.cn/spring-security-ant-url.html

Spring Boot 2 实战: 自定义 Servlet Filter 的两种方式

标签:config   let   配置   注册   pre   pattern   面向对象   资料   作用域   

原文地址:https://blog.51cto.com/14901317/2525163


评论


亲,登录后才可以留言!