程序员

分析为什么采用集群策略 集群Session共享问题 实现SSO

2018-10-15  本文已影响147人  ChaLLengerZeng

原生HttpSession解决集群Session共享问题 实现SSO单点登录

在介绍本节内容之前,在这里谈谈我接触到的一些后端架构出现的问题

就在前两天辅导员早上9点突然发布一条选课通知,到中午12点之前完成大三下学期的选课,好的,我打开了链接想着4个小时的选课时间怎么选不上?然而还真没选上

问题出现

终于请求到了登录页却执行不了登录操作

服务器未响应

项目架构演进示意图

后端项目架构演进

集群产生的问题

Cookie Session策略实现登录逻辑

试想一下这个场景,后端采用Tomcat集群,有5台Tomcat,配置Nginx作为负载均衡服务器,采用权重策略进行反向代理,假如Nginx将一个用户的请求首先转发到了Tomcat1上,用户进行了登录,响应中可以拿到cookie或者set-cookie字段,并且value若是基于Tomcat应用服务器的话,value的值基本都是JSESSION=xxxxxxxx类似的情况,Tomcat底层维护着一个Map,通过这个JSESSIONID寻找属于用户与服务器之间的会话,并get到session对象,就可以实现访问放置在session中的一些用户信息或一些其余别的放置在session中的敏感信息

问题出现

cookie session策略用于解决Http无状态的问题,但是如果集群Tomcat之后,用户如果登录请求被Nginx转发到了Tomcat1上,并且做了登录,那么这个cookie默认情况下会被保存至浏览器的缓存中,直至一次浏览器的生命周期结束cookie将被销毁,但是这个cookie所对应的session会话也只是针对于对客户端/Web与Tomcat1之间,用户登录了,那么之后呢?

如果用户接下来访问个人信息页,这个时候假如配置Nginx的负载均衡策略为权重策略,并且5台Tomcat的权重相同(转发到每一台的几率都相同,还有ip hash等等一些策略去实现负载均衡,这里也不赘述),如果访问个人信息这个请求被Nginx转发到了除Tomcat1之外的任意一台服务器,都会出现一个问题,这个问题是什么大家都可以想一想

继续要求登录

因为请求个人信息这个请求携带的cookie并不能标示Tomcat2上的一次会话,想来也很清楚,这个用户根本没在Tomcat2上做过登录,那这样的话集群带来的代价有点高,这样的话如果集群的规模比较大,也就是说有可能后来访问任何需要验证登录的接口都会判断为未登录,这种情况只要不解决session共享问题,那么都会出现问题

如何解决session共享 实现SSO

github: https://github.com/challengerzsz/Mall 项目可以参考一下

  redisUtil.setRedisValueEx(session.getId(),    JsonUtil.objToString(serverResponse.getData()),
                      Const.RedisCacheExTime.REDIS_SESSION_EXTIME);

这句话是什么意思呢,我封装了一个对RedisTemplate操作的工具类,通过使用RedisTemplate操作Redis,并且设置键值携带过期属性,Redis中的key为登录时会话session的Id,值为将此用户的实例通过封装好的JsonUtil进行序列化后的Json字符串,最终以字符串的形式作为key保存在Redis中

工具类读取Cookie校验的时候,如果有我们手写的Cookie并且有value的情况下,通过调用redis中的get方法去校验这个sessionId是否是登录是我们set进Redis中的值,如果能够从Redis中通过这个sessionId能够get到用户的Json数据,也就说明确实登录过也就防止了伪造,如需使用用户信息的时候,将这个Json字符串反序列化成为实例对象即可

封装CookieUtil读取Cookie的方法

  
  /**
   * 获取属于mall服务器下的cookie 并且返回cookie的值即登录时的sessionId
   * @param request
   * @return
   */
  public static String readLoginToken(HttpServletRequest request
          Cookie[] cookies = request.getCookies();
          if (cookies != null) {
              for (Cookie cookie : cookies) {
                  logger.info("read cookieName :{} cookieValue :{}", cookie.getName(), cookie.getValue());
                  if (StringUtils.equals(cookie.getName(), COOKIE_NAME)) {
                      logger.info("return cookieName :{} cookieValue :{}", cookie.getName(), cookie.getValue());
                      return cookie.getValue();
                  }
              }
          }
          return null;
  }

调用需要校验身份信息的借口时可以这样来操作

@GetMapping("/getInfo")
public ServerResponse<User> getInfo(HttpServletRequest request) {

    String loginToken = CookieUtil.readLoginToken(request);
    logger.error("error {}", loginToken);
    if (StringUtils.isEmpty(loginToken)) {
        return ServerResponse.createByErrorMsg("用户未登录");
    }
    String userJson = redisUtil.getRedisValue(loginToken);
    User currentUser = JsonUtil.stringToObj(userJson, User.class);
    if (currentUser == null) {
        return ServerResponse.createByErrorCodeMsg(ResponseCode.NEED_LOGIN.getCode(), "未登录,需要强制登录");
    return userService.getInfo(currentUser.getId());
}

如果博客中有问题,请私信我一同解决

上一篇下一篇

猜你喜欢

热点阅读