1、问题背景
最近项目开发过程中遇到一个比较奇怪的问题,每次成功登录系统之后,只要一进入某个JSP页面,就会出现session失效,需要重新登录的情况。问题比较奇葩,通过细致查看,发现在该JSP页面中引入了一个不存在的js脚本文件,删除这段JS引用之后,问题修复了,对于问题的起因也就没再继续关注。然而,一次偶然的操作,session失效的问题再次发生,操作流程是登录系统后,由于系统尚在开发过程中,访问了一个还未开发的URL,跳转到系统配置的404页面,此时session失效了。起初由于惯性思维,简单的认为,肯定是404页面中引入了不存在JS或CSS文件,查看之后发现并不存在该问题,由此引发了session失效问题的原理思考。
2、解决思路
根据问题背景的描述,session失效的问题,并不简简单单是引错JS/CSS文件引起的。而通过比较前后两次问题出现的情况,我们发现他们唯一的共同点就是系统抛出了404错误(引入不存在的JS/CSS文件,也会抛404错误)。
再来看下系统session的实现方式。系统做了集群,所以用redis做了分布式session,使用了spring-session-data-redis框架。我们知道,使用spring-session-data-redis框架是需要在web.xml中配置filter的,且是拦截所有请求。spring session的底层原理就是在拦截到请求后,重新包装request和response对象,此时系统中再通过request.getSession()方法获取session,就会使用包装后的request对象,去redis中创建和获取session。具体的实现原理就不过多介绍,可自行百度。我们看下web.xml中的具体配置
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/common/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/views/common/500.jsp</location>
</error-page>
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这里注意filter-mapping的配置,filter-mapping标签下有<dispatcher>子标签,他指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。
<dispatcher> 子元素可以设置的值及其意义:
- REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
- INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
- FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
- ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。我们这里没有配置,也就是使用的默认的REQUEST方式,此时如果系统中有任何调用response.sendError(int error_status_code)
方法时,系统都会通过ERROR的方式去调用相应的servlet。这样的流程是不会被我们配置的filter所拦截的,那此时再去获取session就不会从redis获取,session失效也就是必然了。
问题原理找到了,那修复也就简单了,直接在filter-mapping中增加dispatcher子标签,由于我们是希望使用spring session来全盘管理系统的session,所以这里建议是将所有的dispatch类型都配置上,确保任何方式的请求都会被该filter拦截。
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>