前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot 2 实战:如何自定义 Servlet Filter

Spring Boot 2 实战:如何自定义 Servlet Filter

原创
作者头像
码农小胖哥
修改2020-01-10 10:00:30
7370
修改2020-01-10 10:00:30
举报

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** 就可以了。如下:

代码语言:txt
复制
@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

        };

    }



}

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

2.2 实现 Filter 顺序化

如果需要实现顺序化,可以借助于 **Spring** 提供的 @Order 注解或者 Ordered 接口。这里有一个坑:如果使用 @Order 注解一定要注解标注到具体的类上。

为了方便 **JavaConfig** 风格的声明。我们可以实现 OrderedFilter 接口,该接口是 Filter 接口和 Ordered 接口的复合体,最终上面的配置如下

代码语言:txt
复制
@Configuration

public 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 风格可以参考我的这一篇文章

匹配通过执行具体的逻辑,否则跳过该 **Filter** 。

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

代码语言:txt
复制
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);

    }



    /\*\*

     \* 各自逻辑的函数钩子

     \*

     \* @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<String> urlPatterns = getUrlPatterns();



        if (!CollectionUtils.isEmpty(urlPatterns)) {

            //进行Ant匹配处理

            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();

        }

        // 如果 没有元素 表示全部匹配

        return true;

    }





    public Set<String> getUrlPatterns() {

        return urlPatterns;

    }

}

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

代码语言:txt
复制
@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() {

       // 定义自己的优先级

        return 0;

    }    

}

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

3. Spring Boot的机制

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

代码语言:txt
复制
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

@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 要点

* FilterRegistrationBeanFilter 之间是一对一关系。

* 如果存在多个 FilterRegistrationBean 需要调用其 setName(String name) 为其声明唯一名称,否则只有第一个注册成功的有效。

* 如果需要保证调用顺序可通过调用其 setOrder(int order) 方法进行设置。

4. 总结

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.前言
  • 2. 自定义 Filter
    • 2.1 Filter 的声明
      • 2.2 实现 Filter 顺序化
        • 2.3 自定义 Filter 作用域
        • 3. Spring Boot的机制
          • 3.1 要点
          • 4. 总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档