1、为什么需要Filter
在日常的开发中,我们的项目可能会被各种各样的客户端进行访问,那么,一些带有意图的朋友,就会利用自己所学的技术进行有目的的访问,那么我们的服务端就不再安全和可靠,我相信每位开发者都知道爬虫这种东西,那么当我们的请求不再安全,那么我们后台的数据就会变得透明。
数据透明,是一件多么可怕的事情,在这个数字潮流时代,数据就是金钱,在生活中任何一个系统都会录入我们的个人信息。
那么对请求进行过滤、请求的校验就变得尤为重要。
2、常用的Filter方式
- 在很久以前的Servlet项目中,可以使用@WebFilter注解来进行Filter的配置。
- 在目前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 {@Overridepublic 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 {@Overridepublic 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框架中是非常常见的,这种方式也非常的好用,值得学习。
@Configurationpublic class FilterConfig {@Beanpublic FilterRegistrationBean<UserFilterConfig> userFilterConfigFilterRegistrationBean(){FilterRegistrationBean<UserFilterConfig> userFilter = new FilterRegistrationBean<>();Builder<UserFilterConfig> userBuilder = new Builder<>(userFilter);userBuilder.filterConfiguration(UserFilterConfig.class,1,false,"/*");return userFilter;}@Beanpublic 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、总结
- 使用Builder这种方式对配置类中的数据进行配置,是当前许多框架都在使用的方式,能够在一定程度上隐藏内部的实现。
- FilterRegistrationBean类提供了自定义FIlter的执行顺序,上文的Demo中因为拦截的范围问题,所以不容易看出存在执行顺序的问题,但是想要看到顺序问题也非常的简单,重新给setOrder方法赋值就行了,优先级低的先执行。