Spring Cloud 中的 session 丢失

1,698 阅读2分钟

前言

之前接了一个新需求,就是允许用户打开浏览器访问公司提供的服务,因为我们公司提供的服务基于钉钉使用,需要一系列钉钉之类的系统信息。因此,实现这个需求,需要 copy 一份钉钉的 session数据到浏览器中,生成浏览器独有的 session 信息至 redis 中。大致方向明确,我起手就是干,一顿操作猛如虎,跑起项目点击跳转浏览器访问按钮,现实给了我狠狠一击,弹出一个前端框框, session 信息过期请重新登录。wtf?? 明明复制 session 数据, 生成了一个新的 session 保存到 redis 中,为什么提示数据过期呢。因此我缓缓打出了一个问号

  • session 数据复制失败?
  • session 复制成功 但是保存 redis 失败?
  • session 传输给前端失败?

问题攻坚

排查的方向大致有了,我先打开网页开发控制台查看当前 sessionId ,根据 sessionId 去查询 redis 中保存的数据,一查吓一跳,数据居然没有相关的用户信息,我马上祭出大杀器,debug 它!随着我一步步 debug 至复制数据逻辑代码行,我发现复制数据居然成功。眉头一皱,我感觉这并不简单我立刻记下它的sessionId,放行断点查看网页端显示的sessionId!好嘛,居然牛头不对马尾,完全是两个不同的sessionId,目光缓缓上移,我好像发现问题的本质。

服务器返回的响应结果居然有两个 Set-Cookie 字段这不就是导致了我的 session 被覆盖了吗!此时问题的线索聚焦在了第二个 Set-Cookie ,它究竟是怎么产生的。前端访问到这个接口,主要经过了zuul 网关以及这个微服务,那么网关这个罪魁祸首八九不离十了。

来到zuul这个事故现场,我全局搜索了一下 session ,一下就定位到出祸现场:

   // 保存路由信息
   requestLocal.set(request);
   urlLocal.set(url);
   sessionLocal.set(request.getSession());

好嘛,第一次访问所以该请求没有携带 session 信息,然后网关又记录 session 信息,由于此时没有 session,request.getSession() 这个方法会生成 session ,但是这个 session 不会传递给下游的微服务,下游的微服务它自己也会生成一个 session 。因此,当一个完整的请求结束时,服务器给浏览器的响应结果就会有两个设置 session 字段。那么,我们只要将第一次访问生成的 session 传递给下游微服务,问题就迎刃而解。

修复步骤

  1. 在 spring boot 入口类中加上注解,使得 redis 保存 session 立即生效:
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200, redisFlushMode = RedisFlushMode.IMMEDIATE)
  1. 在第一次访问 zuul 网关的时候设置 header 传递 session:
    ctx.addZuulRequestHeader("Cookie", "SESSION=" +  request.getSession().getId());