SpringBoot1.5.12升级至2.1.9过程中,由DipatcherType引发的ShiroFilter失效问题

目录

背景

问题定位

源码分析

官方文档说明

Dispatcher的作用和影响

再看FilterChain

小结

参考文档


背景

公司有个项目从SpringBoot 1.5.12升级到2.1.9,POM版本修改完成访问项目域名http://ww.xxxx.com/ 直接500。后台日志显示无法Shiro无法从上下文中获取SecurityManager。后来试试别的地址http://www.xxxx.com/admin/test 居然访问正常。查看ShiroFilter的配置: url-pattern /* 。后来发现有个Filter,存在如下代码

request.getDispatcher("/").forward(request,response);

注释掉该行,发现很多功能居然是正常的。也就意味着forward() 时,并未通过ShiroFilter处理。

问题定位

(1)比对升级前后,FilterChain中Filter的数量、类型、顺序,没有问题;

(2)比对实际走过的Filter,发现运行上面那行代码后,实际走过的Filter数量从20个锐减为3个,当时有种见鬼了的感觉。

(3)有个细心的小伙伴发现,Forward后的请求在获取FilterChain时,居然要考虑dispatcher属性是否匹配;排查发现SB 2.1.9中在Filter注册时,设置了Filter的dispatcherType为REQUEST。而在SB 1.5.12中居然是一组值。抱着试试的态度,把除了DisatcherType.ASYNC外的其他值全部设置给ShiroFilter,功能全部正常了。

源码分析

比较两个版本中Filter的注册类AbstractFilterRegistrationBean,SpringBoot 2.1.9实现如下。


    /** 
     * SpringBoot 2.1.9
	 * Configure registration settings. Subclasses can override this method to perform
	 * additional configuration if required.
	 * @param registration the registration
	 */
	@Override
	protected void configure(FilterRegistration.Dynamic registration) {
		super.configure(registration);
        // dispatcherType赋值
		EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
		if (dispatcherTypes == null) {
			dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
		}

		Set<String> servletNames = new LinkedHashSet<>();
		for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) {
			servletNames.add(servletRegistrationBean.getServletName());
		}
		servletNames.addAll(this.servletNames);
		if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
			registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
		}
		else {
			if (!servletNames.isEmpty()) {
				registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
						StringUtils.toStringArray(servletNames));
			}
			if (!this.urlPatterns.isEmpty()) {
				registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
						StringUtils.toStringArray(this.urlPatterns));
			}
		}
	}

SB 1.5.12实现如下


/**
 * SpringBoot 1.5.12-RELEASE
 */
protected void configure(Dynamic registration) {
        super.configure(registration);
        // 关键差异
        EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
        if (dispatcherTypes == null) {
            dispatcherTypes = this.isAsyncSupported() ? ASYNC_DISPATCHER_TYPES : NON_ASYNC_DISPATCHER_TYPES;
        }


        Set<String> servletNames = new LinkedHashSet();
        Iterator var4 = this.servletRegistrationBeans.iterator();

        while(var4.hasNext()) {
            ServletRegistrationBean servletRegistrationBean = (ServletRegistrationBean)var4.next();
            servletNames.add(servletRegistrationBean.getServletName());
        }

        servletNames.addAll(this.servletNames);
        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, (String[])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, (String[])this.urlPatterns.toArray(new String[this.urlPatterns.size()]));
            }
        }

    }
}

正是由于SB1.5.12支持的dispatcherType中包含FORWARD,因此在forward("/")后通过ShiroFilter,所以没有问题。

官方文档说明

在SpringBoot2.1.9的文档中特别说明,如下图所示:

SpringBoot2.1.9说明

但是在SpringBoot2.1.9之前的文档中相关章节并未特别说明,以2.1.8为例仅说明此种Filter的顺序是如何设置的如下图所示:

Registering Servlets, Filters, and Listeners as Spring Beans

Dispatcher的作用和影响

想到此种调整必是规范要求,于是阅读了Java Servlet 3.1 规范中关于DispatcherType的说明,比较精华的一段摘录如下

public DispatcherType getDispatcherType() -
 
// 返回request的DispatcherType
Returns the dispatcher type of a request.
 
// 容器使用dispatcher type来判断需要应用的过滤器。
The dispatcher type of a request is used by the container to select the filters that need to be applied to the request.
 
// 只有dispatcherType和url pattern都匹配的filter才会被应用;
Only filters with the matching dispatcher type and url patterns will be applied.
 
// 考虑到Filter可以配置为支持多个dispatcher type,这样就可以通过dipatcher type来差异化处理请求
Allowing a filter that has been configured for multiple dispatcher types to query a request for
  it’s dispatcher type allows the filter to process the request differently depending on it’s dispatcher type.
 
// 一个请求的dispatcher type 初始值为 REQUEST
The initial dispatcher type of a request is defined as DispatcherType.REQUEST .
 
// forward() 方法产生请求的dispatcher type 为 FORWARD, include() 方法产生请求的dispatcher type为INCLUDE,基于AsyncContext 产生请求的dispatcher type 为 ASYNC
The dispatcher type of a request dispatched via RequestDispatcher.forward(ServletRequest, ServletResponse) or RequestDispatcher.include(ServletRequest, ServletResponse) is given as DispatcherType.FORWARD or DispatcherType.INCLUDE respectively, while a dispatcher type of an asynchronous request dispatched via one of the AsyncContext.dispatch methods is given as DispatcherType.ASYNC .
 
// 最终被dispatch到容器错误处理机制请求的dispatcher type为 ERROR
Finally the dispatcher type of a request dispatched to an error page by the container’s error handling mechanism is given as DispatcherType.ERROR .

简单说就是Filter的选择是基于Filter的url-pattern和dispatcherType 与 request的url和dispatcher是否匹配来选择。一个request在dispatcher过程中其dispatcher type会发生变化。

再看FilterChain

(1)首先FilterChain是一个典型的责任链模式,但在实际的工程中却大不相同;比如责任链中增加类似suppor的函数,实现遍历检查,但实际起作用的仅有1个;再比如Java Web中的filter中sessionFilter和ShiroFilter需要两者组合生效;

(2)以往理解的FilterChain是一个静态的,当url-pattern=/*时,所有请求要面对的FilterChain是一样的。但是现在有个一个二次筛选机制,实际每个请求的FilterChain是动态的;

(3)阅读SpringBoot 2.1.9内嵌Tomcat源码对应的一次forward()处理过程。

场景设定:

Servlet A的url为/a,

Servlet B的url为/b,

图中假设用户请求/a,并在Servlet A中调用 request.getDispatcher("/b").forward(request,response),过程涵盖从ApplicationDispatcher对象创建FilterChain并执行,直到请求响应完成。

小结

本文以DispatcherType为切入点,介绍了Dispatcher的作用场景,以及由此发现FilterChain的动态性。与个人而言,解决了一个工作中的问题,在此记录希望对其他人能够有所帮助。

参考文档

1. SpringBoot各版本参考文档 

https://docs.spring.io/spring-boot/docs/

2. SpringBoot2.1.9 基于SpringBean添加Servlet,Filter,Listener  

https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/html/howto-embedded-web-servers.html#howto-add-a-servlet-filter-or-listener-as-spring-bean

3. Java Servlet 3.1规范 

https://download.oracle.com/otn-pub/jcp/servlet-3_1-fr-eval-spec/servlet-3_1-final.pdf

4. 时序图基于markdown语言编写,通过Visual Studio Code+Markdown Preview Enchener插件预览后截图而成;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值