微服务session落坑记

微服务session落坑记.jpg

本文适用于对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) {
// 略去部分代码
 (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解决方案,将tomcat的session管理机制和分布式session结合起来
  • 走jwt token处理机制,服务端不再存储token,仅做鉴权和刷新
欢迎关注我的微信公众号
68号小喇叭
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 目录Cookie机制什么是CookieCookie的不可跨域名性Unicode编码:保存中文BASE64编码:保存...
    Tomatoro阅读 16,978评论 7 186
  • 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Se...
    chinariver阅读 5,651评论 1 49
  • 转自 :http://blog.csdn.net/taoff/articles/1921009.aspx 一、术语...
    stone_yao阅读 6,251评论 0 31
  • 此时的北京,微凉,走到阳光洒下的地方暖暖的,路过的居民楼旁边的大树,好听的麻雀声听得让人耳朵都痒痒的,还没有长...
    翻滚吧猫猫阅读 183评论 0 0
  • 逻辑后果不是惩罚,是为了让孩子停止不良行为而转移到其他的事情上,因此,如果不能确保孩子的感觉好,那就不要用逻辑后果...
    书媛jy阅读 325评论 0 1