重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章主要介绍了如何解决异步线程获取不到Session问题,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
成都创新互联服务项目包括官渡网站建设、官渡网站制作、官渡网页制作以及官渡网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,官渡网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到官渡省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
我们将原有项目的登录授权功能从Shiro切换到接入SSO单点登录服务并非一帆风顺,因为系统多了,总有一些让我们预想不到的骚操作。
比如这个,在处理请求的线程上启动一个线程,在这个新的线程中获取Session,从Session获取登录用户。
这真的是一个骚操作了!
由于未考虑到这种情况,上线就出了Bug,好在不是很严重的问题。
我们不讨论这样的骚操作有何不好,重要的如何解决问题?找到各种这样的骚操作修改过来?难!没有那么多时间慢慢改,而且改完还需要走一遍测试流程。
关于这件事情我跟同事争论了一番,毕竟之前是没有出现这个问题的,而就切换到SSO(为方便接入SSO服务而封装的SDK)就出现问题,不是我的问题又是谁的问题呢。
上图的代码经测试确实没有问题,测试结果如下图所示。
首先我们要知道,Session ID由服务端创建,并通过响应头cookie响应给浏览器,浏览器将Session ID存储在本地,会在下次请求自动带上Session ID,通过cookie请求头发送给服务端。
在服务端接收到客户端请求时,如果客户端有带上Session ID,那么就根据Session ID获取Session,默认是从内存获取,如果是使用Shiro框架并且使用redis存储Session,那么就是从Redis中获取。
如果Session过期或者客户端没有传递Session ID,则创建新的Session,并会为新的Session重新分配Session ID。
Shrio框架在接收到请求时就通过过滤器拿到Session ID存储到ThreadLocal中了,所以不需要通过HttpServletRequest去拿Session。
Shrio之所以支持在异步线程中还能够获取到Session,这其实是因为Shrio使用的是InheritableThreadLocal,而不是ThreadLocal,实现了将Session ID传递给子线程,因此实现了“异步上下文”传递Session。
但这也是有局限的,要求这个异步线程必须是由当前处理请求的线程创建的,Session ID才能通过InheritableThreadLocal传递给子线程,如果是在线程池中,就不一定能获取到了。
这里留个思考题给大家:为什么说在线程池中不一定能获取到,而不是一定获取不到?要理解这个问题需要对线程池的工作原理、源码,以及InheritableThreadLocal源码理解,因此本篇不展开分析。
所以我们解决Bug就是将ThreadLocal换成InheritableThreadLocal,并且通过方法拦截器(HandlerInterceptor)或者过滤器(Filter)实现set session和remove session操作,推荐后者。
另外,从webmcv框架源码可以看出,RequestContextHolder#getRequestAttributes也是支持InheritableThreadLocal的,只是默认情况下不支持,需要修改配置。
getRequestAttributes方法会尝试从InheritableThreadLocal获取,源码如下。
但能不能获取得到由是否写入决定:
setRequestAttributes方法由RequestContextFilter过滤器调用,该过滤器由webmvc框架自动配置,代码如下。
默认RequestContextFilter并不会将ServletRequestAttributes写入InheritableThreadLocal,代码如下。
因此,我们是否可以替换默认注册的RequestContextFilter,将threadContextInheritable配置为true,这样就能支持将Session ID传递给子线程了,如下代码所示。
但,这并不是有效的,因为在RequestContextFilter之后,DispatcherServlet又调用了一次RequestContextHolder#setRequestAttributes,并且传入的threadContextInheritable为false,清除了前面的写入,所以必须要修改DispatcherServlet的threadContextInheritable为true才支持。但不建议在封装的SDK中这样改动。
关于为什么threadContextInheritable默认为false,官方在RequestContextFilter的API文档给出了如下说明。
感谢你能够认真阅读完这篇文章,希望小编分享的“如何解决异步线程获取不到Session问题”这篇文章对大家有帮助,同时也希望大家多多支持创新互联,关注创新互联行业资讯频道,更多相关知识等着你来学习!