SpringBoot很实用的请求过滤器 - FilterRegistrationBean

发布于:2022-11-29 ⋅ 阅读:(205) ⋅ 点赞:(0)

1、为什么需要Filter

在日常的开发中,我们的项目可能会被各种各样的客户端进行访问,那么,一些带有意图的朋友,就会利用自己所学的技术进行有目的的访问,那么我们的服务端就不再安全和可靠,我相信每位开发者都知道爬虫这种东西,那么当我们的请求不再安全,那么我们后台的数据就会变得透明。
数据透明,是一件多么可怕的事情,在这个数字潮流时代,数据就是金钱,在生活中任何一个系统都会录入我们的个人信息。
那么对请求进行过滤、请求的校验就变得尤为重要。

2、常用的Filter方式

  1. 在很久以前的Servlet项目中,可以使用@WebFilter注解来进行Filter的配置。
  2. 在目前SpringBoot作为后端主流框架而言,使用更多的是配置FilterRegistrationBean类,本文也主要以此类来配置Filter。

两种方式都是针对于Filter接口的实现类而言的。

3、Filter接口

一般我们实现Filter接口,只需要实现doFilter方法即可,但是也可以实现另外两个方法。

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

4、FilterRegistrationBean类

可以看到此类的内部需要一个T类型的filter属性,而这个属性也是FilterRegistrationBean的核心,后面我们只需要将自定义的Filter放入到不同的FilterRegistrationBean中就可以了。

public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<T> {
    private T filter;

    public FilterRegistrationBean() {
        super(new ServletRegistrationBean[0]);
    }

    public FilterRegistrationBean(T filter, ServletRegistrationBean<?>... servletRegistrationBeans) {
        super(servletRegistrationBeans);
        Assert.notNull(filter, "Filter must not be null");
        this.filter = filter;
    }

    public T getFilter() {
        return this.filter;
    }

    public void setFilter(T filter) {
        Assert.notNull(filter, "Filter must not be null");
        this.filter = filter;
    }
}

5、自定义Filter代码实现

5.1、自定义Filter

自定义的Filter不用使用@Bean进行注入

5.1.1、UserFilter拦截对用户信息的请求

public class UserFilterConfig implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("用户过滤器触发成功");
        // 核心代码省略
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

5.1.2、AuthFilter拦截基本的认证信息

public class AuthFilterConfig implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("认证过滤器触发成功");
        // 核心代码省略
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

5.2、配置FilterRegistrationBean类

对于不同的Filter对象需要配置不同的FilterRegistrationBean类,因为存在重复代码,所以我进行了代码提取,并且向容器中注入相应的对象。
在此配置类中我使用到了Builder这种方式来进行数据的配置,这种方式在当前的SpringBoot框架中是非常常见的,这种方式也非常的好用,值得学习。

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<UserFilterConfig> userFilterConfigFilterRegistrationBean(){
        FilterRegistrationBean<UserFilterConfig> userFilter = new FilterRegistrationBean<>();
        Builder<UserFilterConfig> userBuilder = new Builder<>(userFilter);
        userBuilder.filterConfiguration(UserFilterConfig.class,1,false,"/*");
        return userFilter;
    }

    @Bean
    public FilterRegistrationBean<AuthFilterConfig> authFilterConfigFilterRegistrationBean(){
        FilterRegistrationBean<AuthFilterConfig> authFilter = new FilterRegistrationBean<>();
        Builder<AuthFilterConfig> authBuilder = new Builder<>(authFilter);
        authBuilder.filterConfiguration(AuthFilterConfig.class,6,false,"/test/*");
        return authFilter;
    }


    private class Builder<T extends Filter>{

        private FilterRegistrationBean<T> filterRegistrationBean = null;

        public Builder(FilterRegistrationBean<T> filterRegistrationBean){
            this.filterRegistrationBean = filterRegistrationBean;
        }

        public Builder filterConfiguration(Class<? extends Filter> clazz,int order,boolean async,String ...patterns){
            T filter = null;
            try {
                filter = (T)clazz.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                System.out.println("[ " + clazz.toString() + " ] 过滤器对象不存在");
            }
            this.filterRegistrationBean.setFilter(filter); // 设置过滤器
            this.filterRegistrationBean.setOrder(order); // 设置启动顺序
            String clazzPath = clazz.toString().toLowerCase(Locale.ROOT);
            // 配置过滤器的名称,首字母一定要小写,不然拦截了请求后会报错
            this.filterRegistrationBean.setName(clazzPath.substring(clazzPath.lastIndexOf(".")));
            this.filterRegistrationBean.addUrlPatterns(patterns); // 配置拦截的请求地址
            return this;
        }

    }
}

6、运行结果

6.1、Controller类如下:

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FilterDemoController {

    private final ApplicationContext applicationContext;

    @GetMapping(value = "/abc")
    public void show(){
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            if(beanDefinitionName.contains("Filter")){
                System.out.println(beanDefinitionName);
            }
        }
        System.out.println("=====> end <=====");
    }

    @GetMapping(value = "/test/abc")
    public void test(){

    }
}

/abc:会打印当前容器中所有的Filter对象。
/test/abc:什么也不做。

6.2、控制台显示

当我访问http://localhost:8080/abc时,就会触发UserFilter这个过滤器,结果如下:
在这里插入图片描述

可以看到,过滤器会先触发,然后打印出所有的Filter,容器中会存在两个不同的FilterRegistrationBean。

当我访问http://localhost:8080/test/abc时,就会触发AuthFilter这个过滤器,结果如下:
在这里插入图片描述

耶??为啥结果不是想象的那样??
这是因为我的UserFilter的拦截路径为/*,而AuthFilter的拦截路径为/test/*
那为什么UserFilter会在AuthFilter之前执行呢?
因为/*的拦截范围比/test/*的范围大,可以说/test/*是经过了/*拦截过再进行了匹配拦截。于此同时,我在相应的FilterRegistrationBean中也设置了Filter的执行顺序。

7、总结

  1. 使用Builder这种方式对配置类中的数据进行配置,是当前许多框架都在使用的方式,能够在一定程度上隐藏内部的实现。
  2. FilterRegistrationBean类提供了自定义FIlter的执行顺序,上文的Demo中因为拦截的范围问题,所以不容易看出存在执行顺序的问题,但是想要看到顺序问题也非常的简单,重新给setOrder方法赋值就行了,优先级低的先执行
本文含有隐藏内容,请 开通VIP 后查看