登录状态的保存

# 登录状态的保存专题 ## 为什么要保持登录状态? > 因为很多请求都是受保护的请求, 这里受保护具体指的是必须要有登录权限才能做的一些操作, 如购物车, 订单, 评论等请求, 因此, 我们需要保持登录状态, 来判断是否有与之相应的权限 ## 如何保持登录状态 > 最传统的方式是使用`session`会话域来保持登陆状态, 因为session的作用范围一般是浏览器启动到关闭 > 注意: <font color='red'>**session的销毁机制是延迟销毁, 默认30min, 30min内如果没有再次访问, 就会删除, 而浏览器范围内的原因是cookie默认的生命周期为内存, 即浏览器开启到结束, 且通过JsessionId这个cookie获取的session, 所以session的生命周期是浏览器**</font> > 而且, 传统原生的session都是基于本地内存的, 我们可以在登陆成功后将用户信息保存在session, 用于维持登陆状态 ## session理论 ### 原理 ![image.png](https://cos.easydoc.net/13568421/files/llq7vowz.png) > 概括: session和本地内存相似, key是名字叫jessionId的cookie, value是一个哈希表, 整理而言也是一个哈希表, 而value的key是会话域数据的key, value就是值, 访问会话域有如下步骤 1. 通过jessionid的cookie找到对应的会话域 2. 通过key找到对应的value ### 传统session的情况下, 为什么登陆了, 首页没有登录状态? > 1 **`cookie`的角度:** 因为在默认情况下, `cookie`的作用范围是当前域名下的所有路径及其子域名的所有路径, 但是, auth.bitmall.com和www.bitmall.com的域名完全不同, cookie的作用范围不够大, 即, 在www.bitmall.com发送请求的时候不会携带cookie > 2 **`session`角度:** 因为这里处于两个微服务, 传统session存储在独立的内存中, 两个微服务有两个独立的session内存, 因此, 第二个独立的session获取不到登陆状态 ### session共享问题 ![image.png](https://cos.easydoc.net/13568421/files/llq7zcuf.png) > 1. 在同域名的情况下, 确实可以携带cookie, 但是因为原生session是本地内存的原因, 在分布式场景下, 即使有cookie, 也会因为内存间不同步而获取不到登陆状态 > 2. **(同一个父域名的前提下)** 不同域名的情况下, 主要是cookie的携带问题 ### session共享问题的解决方案-1 <i>session复制</i> > **前提: 相同域名的前提下!** ![image.png](https://cos.easydoc.net/13568421/files/llq86hn3.png) #### 优点 1. session复制是原生支持的, 不需要引入额外的框架, 可以让系统更加轻量级 #### 缺点 1. 占用大量的带宽, 因为session复制会时刻将session里面的数据同步到其他微服务的session, 这样就会产生网络传输, 一旦服务器多了, 且用户访问量大, 那么, 后面sesson同步就会一直数据传输, 占用带宽多, 最终导致整个系统的延迟变高, 吞吐量变低 2. 占用大量的内存, 如果有一百个微服务, 每一个实际有1G的用户数据, 那么需要100G去存储, 99G冗余存储, 非常浪费内存 3. 无法水平扩容, 因为session复制通俗而言即保存到一个session的同时复制到其他session, 都是基于当前数据的, 如果运行过程中水平扩容, 即加入一个微服务, 之前的数据就没了, 因此无法水平扩容 > **总结: 弊大于利** ### session共享问题的解决方案-2 *客户端存储* ![image.png](https://cos.easydoc.net/13568421/files/llq8mdgg.png) #### 优点 1. 不会占用服务器资源, 没有任何的内存占用, 不可能存在内存浪费 #### 缺点 1. cookie保存数据的长度是有限的, 如果保存用户数据, 可能会造成溢出的情况, 导致用户信息保存的不完整 2. 以cookie的形式存储在浏览器, 响应回来的cookie通常非常的大, 即相应的数据很大, 会占用大量的带宽, 导致延迟变高, 吞吐量变低 3. 以cookie的形式存储到浏览器, 是一件极其危险的事情, 无论是传输过程还是存储过程, 都极大可能被窃取 > **总结: 安全性极差, 最不能采用的一种方法** ### session共享问题的解决方案-3 *ip_hash一致性* > ip_hash是NGINX负载均衡的一种策略, 这种策略是通过计算用户ip的哈希值,然后匹配到某一个应用服务器上, 一个ip对应着一台服务器 ![image.png](https://cos.easydoc.net/13568421/files/llq8we3y.png) #### 优点 1. ip_hash只需要改NGINX的配置文件, 不需要动源码 2. 支持服务的水平扩展 #### 缺点 1. 如果进行了水平扩展, 有可能同一个hash值分配到的服务器发生改变, 这样需要重新登陆, session问题就会出现 2. 如果某一台服务器宕机重启了, 这台服务器的用户需要重新登陆 > **总结: 总体而言利大于弊, 下面这些问题发生概率不大, 可以考虑采用** ### session共享问题的解决方案-4 *统一存储* ![image.png](https://cos.easydoc.net/13568421/files/llq93n1l.png) #### 优点 1. 数据非常的安全, 除非redis被攻破了 2. 支持水平扩展, 如果如何扩展, session都能保持一致 3. 微服务重启不会导致session丢失 ### 缺点 1. 增加了一个中间件, 有网络传输的环节, 整体延迟变高 > **总结: 相较于前面的方案, 该方案最好, 缺点的延迟不足挂齿** ### 不同子域名session共享问题的解决 ![image.png](https://cos.easydoc.net/13568421/files/llq98vek.png) > ***一句话, 扩大cookie的作用范围*** >** 最终采取的方案: 扩大cookie作用范围, 并采用统一存储** ## 配置SpringSession [官网配置指导](https://docs.spring.io/spring-session/reference/2.7/guides/boot-redis.html) > 彩蛋: ![image.png](https://cos.easydoc.net/13568421/files/llq9kgum.png) 这里说明了HttpSession被替换了 > 注意, 因为这里的版本和我们的版本不一样, 所以有些配置项不一定是一样的, 仔细斟酌 ### 配置相关问题-1序列化异常 ![image.png](https://cos.easydoc.net/13568421/files/llq9t4dr.png) > 序列化异常的原因是存储到Redis默认需要将Java对象序列化后存储, 而对应的VO没有实现序列化接口 > 这也引发了问题, 我们不应该采取序列化的方式, 因为如果以序列化的形式存储, 扩展性极差, 其他语言写的微服务可能就读不到这部分会话信息, 而且可读性也差 ### 优化 #### 优化方向 1. Redis序列化形式 2. cookie的作用范围 ```java @EnableRedisHttpSession @Configuration public class SessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericFastJsonRedisSerializer(); } @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); cookieSerializer.setDomainName("bitmall.com"); // 这里设置父域名, 扩大cookie的作用范围 cookieSerializer.setCookieName("bitmall-login-sessionId"); return cookieSerializer; } } ``` ##### 问题-配置类配置好了后序列化错误 > **因为序列化的名字必须是`springSessionDefaultRedisSerializer`这个才行**, 否则组件无法注入 ##### 问题-配置类配置好了后反序列化错误 > **因为其他微服务没有配置序列化的方式, 默认用Java的反序列化, 应该把对应的配置类拷贝到所需要使用的微服务中** ##### 问题-配置类配置好了为什么会话域有错误信息 > **因为RedirectAttributes放入了会话域** ##### 问题-配置类配置好了为什么获取不了数据 > **`cookieSerializer.setUseSecureCookie(true); // 标记为安全的cookie`的**这段代码, 浏览器无法正常携带cookie, 因此删除即可 ##### 注意 > 必须严格按照上面的CV, 否则会错误 ## 其他知识 ### 什么是魔法值 > 魔法值可以理解为一些存在其那套逻辑的使用量, 就像是这个session的key, 因为这个key的定义, 以后都要用这个key名字, 所以这个key是魔法值 ## SpringSession原理 [原理图](https://www.processon.com/embed/64e87a2037c9b75d18db1a38) > **总结: 装饰者模式**