SpringBoot使用Filter的坑

业务需求背景:
项目采用微服务架构,在各个服务前面配置一个网关,通过SpringCloud生态中的Zuul组件实现。
该网关同时负责页面调度,在各个单页面应用子产品的页面之间进行调度。
ZuulFilter挺有意思,对于本服务的Controller请求不会进行拦截,因此需要针对页面请求做一个认证鉴权的Filter。

实现第一版

首先实现一个Filter进行鉴权及页面重定向(未登录认证状态下跳转到登录页面)。
大体逻辑如下:
①通过WebFilter进行Filter声明,这样容器在进行部署的时候就会处理该Filter,创建实例并创建配置对象FilterConfig,然后会将该Filter应用到urlPatterns所指定的url;
②在init方法中获取到初始化参数,自定义的excludedUrls,作为成员在后续执行过滤逻辑的时候使用;
③在doFilter中进行url的鉴定,如果需要执行认证鉴权处理,则执行相应逻辑。不满足条件的情况下重定向到登录页;
④Filter类增加Component注解,让该Filter被容器管理。

@Component
@WebFilter(filterName = "WebAuthFilter", urlPatterns = "/web/*",
        initParams = {
            @WebInitParam(name = "excludedUrls", value = "/web/login")
        }
)
public class WebAuthFilter implements Filter {

    private List<String> excludedUrlList;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String excludedUrls = filterConfig.getInitParameter("excludeUrls");
        excludedUrlList = Splitter.on(",").omitEmptyStrings().splitToList(excludedUrls);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String url = ((HttpServletRequest) request).getRequestURI();
        if (excludedUrlList.contains(url)) {
            chain.doFilter(request, response);
        } else {
            String sToken = ((HttpServletRequest) request).getHeader("Authorization");
            if (sToken != null) {
                Map<String, Object> map = TokenUtils.parseToken(sToken);
                if (map == null) {
                    ((HttpServletResponse)response).sendRedirect("/web/login");
                }
            } else {
                ((HttpServletResponse)response).sendRedirect("/web/login");
            }
        }
    }

    @Override
    public void destroy() {

    }
}

然后在SpringBoot的Application中增加注解@ServletComponentScan,这样容器会扫描到@Component注解的Filter。

问题出现

出现的问题是:访问的url为/user/*或者/product/*的时候,该过滤器也执行了!
也就是说,WebFilter注解配置的urlPatterns没有起作用。
问题定位:
在查看容器启动日志的时候,发现WebAuthFilter被两次注册,两次映射:
这里写图片描述
从上图可以看到,WebAuthFilter这个Filter是我们自己定义的,它被做了两次映射,而且两次映射的名字不同(WebAuthFilter和webAuthFilter),分别映射到的URL是“/web/*”和”“/*”。其中WebAuthFilter是我们自己命名的。
这样就解释了为什么所有的URL都会被该Filter处理。

问题定位

WebAuthFilter的第一次映射容易理解,是我们自己通过@WebFilter定义的。
那么webAuthFilter是谁给映射的呢?
必然是Spring容器处理的。
在跟踪源码的时候找到AbstractFilterRegistrationBean抽象类,该类中有一个方法onStartup,应该是容器启动的时候执行的,做的是一些Bean注册的工作。该方法最后调用了configure,在该方法中进行了映射处理。

if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
            this.logger.info("Mapping filter: '" + registration.getName() + "' to: "
                    + Arrays.asList(DEFAULT_URL_MAPPINGS));
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                    DEFAULT_URL_MAPPINGS);
        }
        else {
            if (!servletNames.isEmpty()) {
                this.logger.info("Mapping filter: '" + registration.getName()
                        + "' to servlets: " + servletNames);
                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
                        servletNames.toArray(new String[servletNames.size()]));
            }
            if (!this.urlPatterns.isEmpty()) {
                this.logger.info("Mapping filter: '" + registration.getName()
                        + "' to urls: " + this.urlPatterns);
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                        this.urlPatterns.toArray(new String[this.urlPatterns.size()]));
            }
        }

在servletNames和urlPatterns为空的情况下,进行了缺省映射,即映射到“/*”。
置于servletNames和urlPatterns为空的情况,这里没有深究了。
那么,为什么会出现定义的WebAuthFilter被两次注册的情况呢?
仔细分析了一下,认为可能的原因是:@Component和@WebFilter双重注册导致的。

解决办法

解决办法一@WebFilter

在这种情况下,去掉了@Component注解,再次启动服务。查看日志,发现该Filter仅被映射一次,通过浏览器访问相应的url也表现正确。

解决办法二@Component

这种情况下,保留了@Component注解,那么要进行配置的urlPatterns怎么处理呢?
通过FilterRegistrationBean进行@Bean声明,查看源码知道,onStartup进行注册的时候,实际上也是找到了各类RegistrationBean然后分别注册,配置映射。
有各种类型的RegistrationBean:
①AbstractFilterRegistrationBean;
②FilterRegistrationBean;
③ServletListenerRegistrationBean;
④ServletRegistrationBean;
那么我们自然可以通过自声明一个FilterRegistrationBean来进行注册。这种处理方式如下:
去掉FIlter上的@WebFilter注解,增加如下的Configuration类:

@Configuration
public class WebAuthFilterConfig {

    @Bean
    public FilterRegistrationBean webAuthFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(webAuthFilter());
        registration.setName("WebAuthFilter");
        registration.addUrlPatterns("/web/*");
        registration.addInitParameter("excludeUrls", "/web/login");
        registration.setOrder(0);
        return registration;
    }

    @Bean
    public Filter webAuthFilter() {
        return new WebAuthFilter();
    }
}

如此处理,也能达到同样的效果。

经过对比,当然第一种解决方案更直白,更简洁。

后述:网上的很多东西都是带着坑的,直接搬过来用真的有风险!

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Spring Boot中使用Filter,可以通过两种方式进行配置: 1. 注解方式 可以使用@WebFilter注解声明Filter,然后通过@ServletComponentScan注解启用自动扫描。 示例代码如下: ``` import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(filterName = "myFilter", urlPatterns = "/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化操作 } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 过滤操作 chain.doFilter(request, response); } @Override public void destroy() { // 销毁操作 } } ``` 2. 配置类方式 可以创建一个配置类,实现WebMvcConfigurer接口,在其中重写addInterceptors方法,并添加FilterRegistrationBean注册Filter。 示例代码如下: ``` import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; @Configuration public class MyFilterConfig { @Bean public FilterRegistrationBean<Filter> myFilter() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); registration.setFilter(new MyFilter()); registration.addUrlPatterns("/*"); registration.setName("MyFilter"); registration.setOrder(1); return registration; } } ``` 其中,addUrlPatterns方法设置Filter的拦截路径,setName方法设置Filter的名称,setOrder方法设置Filter的执行顺序。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值