微服务session落坑记

微服务session落坑记

技术杂谈小彩虹2021-07-15 8:40:0570A+A-

本文适用于对session、cookie有一定了解的同学,主要以问题定位过程为线索,简单讲述tomcat session生成机制、oauth2认证过程以及spring方法参数映射处理等内容

背景知识

  • session:由于http协议无状态,为了保存用户状态信息,web容器支持session管理机制,当客户端请求web应用时,如果在处理过程中调用了request.getSession()方法,则web容器会先根据url或者cookie中上传的JSESSIONID(默认,可以另行设置,该值会被设置到request对象的requestedSessionId字段)查找对应session,如果获取不到将自动创建一个session对象;当session过期或被放弃后,服务器将终止该session
protected Session doGetSession(boolean create) {
    // 略去部分代码
    if (requestedSessionId != null) {
        session = manager.findSession(requestedSessionId);
        // 略去部分代码
        String sessionId = getRequestedSessionId();
        // 略去部分代码
        session = manager.createSession(sessionId);

        // 创建cookie并写入response
        if (session != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());
            response.addSessionCookieInternal(cookie);
        }
        // 略去部分代码
    }
  • cookie:为了服务器能够识别不同的客户端,客户端通过cookie保存服务端返回的数据(如JSESSIONID,tomcat服务器默认的session对应的cookie key为JSESSIONID),然后服务端通过客户端请求时放在http header中的cookie数据找到之前为客户端创建的session;cookie分为会话cookie和持久cookie,会话Cookie浏览器会话有效期间存在,持久cookie服务端会设置http header 缓存相关字段指示客户端缓存策略,cookie传到客户端后,保存在某个目录下
  • oauth2:oauth是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用,可以用于微服务环境下的公共鉴权,而本文服务鉴权走的就是oauth2

前人挖坑后人落,都是框架惹的祸

问题特征

  • 需求上线在即,发现某个接口请求后会新生成JSESSIONID并以Cookie形式回写浏览器,导致后续请求鉴权失败,必现
  • 服务相互调用关系比较复杂,需要在开发环境测试该功能,本地没有测试

问题定位过程:

  • 交流得知,此前定位过程中,将被请求方法体代码全部注销后,问题仍然存在,于是错过了查看问题代码(其实是方法参数出了问题),尽早定位出问题的好机会
  • 怀疑是否nginx会话保持策略导致的问题(但是该策略应该会对所有请求均产生影响,而不只是单个请求),查看nginx.conf配置,该服务没有走负载均衡
  • 怀疑是否nginx对该请求路径(路径中包含auth敏感字段)做了特殊配置,但查看配置,nginx对于该服务请求路径配置规则很简单,且将请求路径中auth字段删掉后测试,问题仍然存在
  • 怀疑是否有外围代码重写了cookie,搜索没找到相关代码
  • 梳理服务鉴权过程(见top图,图中做了缩略,将公共认证服务删除,直接对接oauth2),描述如下:
    1)认证鉴权逻辑被封装在了公共Filter中,对所有请求进行拦截,判断是否已登录
    2)如果没有上传JSESSIONID或者无法据其在redis找到session及token信息,返回客户端重定向到login接口
    3)客户端调用login接口:服务端调用oauth2(其实有个公共的鉴权服务)认证合法性,将认证返回的token信息融合自身的JSESSIONID写入redis,随后将服务端JSESSIONID写回客户端cookie,坑:此处复用了tomcat默认的JSESSIONID,推测是为了复用request的getRequestedSessionId方法,该方法会直接取客户端cookie提交的JSESSIONID,业务中多次用到了getRequestedSessionId方法,tomcat session和服务认证返回session除了都叫JSESSIONID外,没有半毛钱关系,但是因为重名,相互之间会覆写
    4)客户端带着认证返回的JSESSIONID继续调用之前的业务接口,通过认证,走业务逻辑,其实此时tomcat中的JSESSIONID和上传的JSESSIONID不一致,根据上传的JSESSIONID在tomcat是找不到对应session的
    5)认证过程还有其他逻辑,此处做了删减,不影响主体流程
  • 梳理之后,发现二者之间虽然key一致但是值不同,而且彼此会干扰,于是怀疑外围代码调用了getSession方法,搜索代码未找到
  • 查看请求路径对应方法,方法中使用了session参数,但是方法体中未使用该参数,联想到spring的参数值自动映射机制(见ServletRequestMethodArgumentResolver),在为session赋值的过程中调用了getSession方法,进而由于在tomcat找不到对应session而新建、回写相关cookie到客户端
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    Class<?> paramType = parameter.getParameterType();
    // 略去部分代码
    if (HttpSession.class.isAssignableFrom(paramType)) {
        HttpSession session = request.getSession();
        // 略去部分代码
    }
}
  • 删除参数,测试,问题解决

  • 使用该认证机制的服务的业务方法中不要使用tomcat的session机制(如getSession或session参数),否则就会出现这个问题

解决方案

  • 针对此问题,将session参数去掉
  • 将认证机制的JSESSIONID换成另外一个名字,但是改动量比较大,可能会有其他坑
  • 走分布式session(token)解决方案,将tomcat的session管理机制和分布式session(token)结合起来
  • 采用jwt token形式鉴权,服务端不保存token,仅做验证和刷新
欢迎关注我的微信公众号

68号小喇叭

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们