当前位置: 首页 > 科技观察

解决异步线程无法获取Session

时间:2023-03-19 17:41:05 科技观察

的问题我们将原项目的登录授权功能从Shiro切换为访问SSO单点登录服务。并不是一帆风顺的,因为有很多系统,总有一些我们意想不到的意外操作。比如在处理请求的线程上启动一个线程,在这个新线程中获取Session,从Session中获取登录用户。这真是作秀啊!因为没有考虑到这种情况,上线的时候出现了bug。幸运的是,这不是一个严重的问题。我们不讨论这样的表演操作有什么问题。重要的是如何解决问题?找各种这样的show操作修改一下?难的!没有那么多时间慢慢改,而且改完之后还需要走测试流程。为了这件事,我和同事发生了争执。毕竟之前没有出现过这个问题,但是当我切换到SSO(为了方便访问SSO服务而封装的SDK)时,出现了问题。这不是我的问题,也不是谁的问题。那问题呢。上图中的代码经过测试没有问题,测试结果如下图所示。首先我们要知道SessionID是由服务器创建的,通过响应头cookie响应给浏览器的。浏览器将SessionID存储在本地,下次请求时自动带上SessionID,通过cookie请求头发送给服务器。服务器端收到客户端请求时,如果客户端有SessionID,则根据SessionID获取Session,默认从内存中获取。如果使用Shiro框架,使用Redis存储Session,则从Redis中获取。如果Session过期或者客户端没有传递SessionID,则会创建一个新的Session,并为新的Session重新分配SessionID。Shrio框架在收到请求时,通过过滤器获取SessionID,并存储在ThreadLocal中,所以不需要通过HttpServletRequest获取Session。Shrio之所以支持在异步线程中获取Session,其实是因为Shrio使用InheritableThreadLocal而不是ThreadLocal来传递SessionID给子线程,所以实现了“异步上下文”来传递Session。但这也是有限制的。要求这个异步线程必须由当前处理请求的线程创建。SessionID可以通过InheritableThreadLocal传递给子线程。如果在线程池中,可能获取不到。这里给大家提一个思考题:为什么在线程池中不一定可用,但不一定不可取?理解这个问题需要了解线程池的工作原理、源码、InheritableThreadLocal源码,本文不展开分析。所以我们通过将ThreadLocal替换为InheritableThreadLocal来解决bug,通过方法拦截器(HandlerInterceptor)或过滤器(Filter)实现设置会话和移除会话操作。推荐后者。另外从webmcv框架的源码可以看出,RequestContextHolder#getRequestAttributes也支持InheritableThreadLocal,但是默认不支持,需要修改配置。getRequestAttributes方法会尝试从InheritableThreadLocal中获取,源码如下。但是能不能获取到就看有没有写了:setRequestAttributes方法是由Webmvc框架自动配置的RequestContextFilter过滤器调用的,代码如下。默认的RequestContextFilter并没有将ServletRequestAttributes写入InheritableThreadLocal,代码如下。因此,我们可不可以替换掉默认注册的RequestContextFilter,将threadContextInheritable配置为true,这样就可以将SessionID传递给子线程了,如下代码所示。但是这样并没有效果,因为在RequestContextFilter之后,DispatcherServlet再次调用了RequestContextHolder#setRequestAttributes,传入的threadContextInheritable为false,清除了之前的写法,所以需要修改DispatcherServlet的threadContextInheritable为true来支持。但是不建议在打包好的SDK中做这样的改动。关于为什么threadContextInheritable默认为false,RequestContextFilter的官方API文档给出了如下解释。本文转载自微信公众号“爪哇艺术”,可通过以下二维码关注。转载本文请联系爪哇艺术公众号。