使用的shiro框架,在登录过程中由于需要多次查询principal对应的用户信息,就把第一次查询的结果放到threadLocal中了,为了保证threadLocal及时得到清理,系统中每一次Request的前后都加了拦截器Interceptor去remove threadLocal。原本以为登录这个请求也会被拦截器过滤,结果并不会。导致在很小几率的情况下,一个线程连续处理2个用户的登录请求,第二个用户登录拿到的是第一个用户信息(threadLocal中有值,直接拿了)。为什么说几率很小?因为登录请求比业务请求次数少很多,一个线程只要处理了业务请求,threadLocal就会被拦截器清空了。
那为什么登陆请求就不能被拦截器拦截呢?
原因就在于servlet容器里Filter和Servlet(拦截器)的执行顺序。在servlet容器里一般有Listener, Filter和Servlet,Listener最先执行,Filter次之,Servlet最后,而拦截器恰恰就是在Filter之后Servlet之前执行,我们用的是CXFServlet,SpringMvc的DispatcherServlet应该也一样。登录请求先经过ShiroFilter,在shiroFilter里处理登录,这样threadLocal中的信息还没来得及被拦截器清空,从而导致串号问题的发生。
那为什么后置Interceptor没清空threadLocal呢? 我们PC登录用的是jsp,而CXFServlet没有拦截jsp请求。
怎么做才保险呢?
- 不用threadLocal,threadLocal是个好东西,但也存储内存泄露和线程污染的风险,使用一定要谨慎,使用前后都要清空。
- 在Filter里清空threadLocal,这个filter还要在shiroFilter之前定义,团队要定好规范,不要随意改动顺序
- 在登录前后再次清空threadLocal
遇到这种小概率偶发的bug复现很麻烦,只能比较这次发版你改了啥,然后再去分析原因,ps:框架代码还是要一开始就设计的好一点,后期改动出bug会给用户造成坏印象。