分布式下的Session解决方案
单点登录在单机模式下是非常好解决的,毕竟只要在单机的Session中存储一份即可,而在分布式下单点登录由于相同服务的机器众多,不可能每个服务器每次都会对同一个服务器服务,所以分布式下的单点登录就成为了一个问题.
单点登录
一般依靠Cookie或者Session实现(Session实际也依靠Cookie,毕竟使用JSESSIONID帮助SESSION进行判断.
解决方案
-
Session复制
通过不同服务期间的Session进行同步,进而实现Session的共享
- 优点:
- web-server(Tomcat)原生支持,只需要修改配置文件即可
- 缺点:
- session同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力
- 任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平拓展更多的web-server
- 大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取
- 优点:
-
客户端存储
通过存储在用户本地的Cookie中,每次请求携带Cookie中的信息,访问后端程序进行共享
- 优点:
- 服务器不需存储session,用户保存自己的session信息到cookie中,节省服务端资源
- 缺点:
- 每次http请求,携带用户在cookie中的完整信息,浪费网络带宽
- session数据放在cookie中,cookie有长度限制4k,不能保存大量信息
- session数据放在cookie中,存在泄露,篡改,窃取等安全隐患
- 优点:
-
hash一致性
通过nginx进行session共享
- 优点:
- 只需要改ngain配置,不需要修改应用代码
- 负载均衡,只要hash属性的值的分布是均匀的,多台web-server的负载时均衡的
- 可以支持web-server水平扩展(session同步法师不行的,受内存限制)
- 缺点:
- session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录
- 如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
- 优点:
-
统一存储
利用程序员经典思路,外面套一层,再次存在另一个地方
- 优点:
- 没有安全隐患
- 可以水平扩展,数据库/缓存水平切分即可
- web-server重启或者扩容都不会有session丢失
- 缺点:
- 增加了一次网络调用,并且需要修改应用代码,如将所有的getSession方法替换为从Redis查数据的方式.redis获取数据比内存慢很多
- 优点:
-
jwt登录
-
Oauth2登录
Spring Session
这里我们采用Redis来实现共享Session,也可以存储在别的位置
根据官网我们这里首先引入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
由于是Spring Boot2项目所以我们这里写配置文件
spring.session.store-type=redis
同时也可以配置其他相关信息例如,会话超时时间,会话续期的模式,存储在redis中的key值
当然也可以基于JavaConfig进行配置,这里我们配置了存入Redis的序列化方式
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
这里的配置根据源代码中的
@Autowired(required = false)
@Qualifier("springSessionDefaultRedisSerializer")
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
this.defaultRedisSerializer = defaultRedisSerializer;
}
进行了序列化方式的配置
这样我们就可以在Controller参数中添加*HttpSession* session
即可在多个服务中存储session,当需要session时会去Redis中取出数据
如果需要定义传到前端的Cookie的配置信息
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID"); // <1>
serializer.setCookiePath("/"); // <2>
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); // <3>
return serializer;
}
Spring Session原理
核心类
SessionRepositoryFilter
核心代码
这里可以看到它使用这个过滤器单次过滤将每次Session的操作使用
RedisSessionRepository
来进行操作
而过滤操作则使用装饰者模式进行处理,如果写过XSS攻击的过滤器的话肯定会知道这样操作的意义
这个方法则替换了原有的session,每次获取从redis中获取
Q.E.D.