1 概述
这一篇基于《SpringSecurity in Action》一: 基于Session实现登陆认证的简单实现
SpringSecurity基于session实现登陆认证时,有一个问题就是无法实现分布式部署,因为session是存储在某一个节点(或是某一个服务副本)的内存里的。如有这么一个用户服务,有三个副本,当进行登陆时,登陆请求打在了副本1,而进行用户列表请求时,请求打在了副本2,这时副本2是没有副本1内存中的session信息的,所以请求会失败。这当然是一个不折不扣的BUG。
2 方案
针对该问题,目前有三种解决方案:
-
Session固定: 看有些地方也叫Session粘滞。这里的Session固定跟固定Session攻击可不是一个概念,这里是指根据算法,把某个用户登陆时,请求打在了哪个副本上,以后就让它的请求永远都打在这个副本上,这样就不会出现登陆打在副本1,其它接口打在副本2的情况了。这种方案的缺点就是可能会造成负载不均衡。
-
Session同步: 这种方案是当登陆完成后,把副本1里的session对接同步到其它副本上去,一看就知道会面临数据一致性问题,而且副本多的话,这个问题会越发明显。
-
Session共享: 这种方案是当登陆完成后,处理登陆请求的那个副本,把sessin信息放到一个共享的存储位置,其它副本可以从这个位置读取到某用户的session信息。这个方案会不会存在数据一致性问题,就在于向共享存储写session信息时,是同步的还是异步的。如果是异步的,就会存在数据一致性问题。不过话又说回来,我们为什么要把这个操作设计成异步呢?完全没有必要没有困难制造困难也要上嘛。使用内存型的共享存储,同步延时可以很小的嘛。相信不会有人把业务服务器部署在北京,然后共享存储部署了深圳吧。
session固定示意图
session同步示意图
session共享示意图
3 方案选择
目前使用比较多的方案是Session共享,Session固定跟Session同步两种方案我个人并没有实践过,所以这里只说Session共享这一种方案。
虽然Session共享的原理很简单,但我们依然没有必须自己去实现一套这样的方案来,Spring已经提供了这种方案的实现。对应的是 spring-session-data-redis
.
主要依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
spring-session-data-redis
依赖引入后,SpringBoot会自动整合以Redis为共享存储的Session共享方案,要是不放心,也可以再加个注解@EnableRedisHttpSession
在启动类或是其它配置类上。
配置文件
spring:
redis:
host: 256.73.82.27 # 地址,这个ip是瞎写的,第一位是256就不可能的对吧
port: 6379 # 端口
database: 1 # 数据库索引
password: redis
session:
store-type: redis
这样,引入依赖,加上配置项,就差不多完成Session共享的开发工作了。接下来测试一下。启动项目,登陆一下,效果如下:
登陆完成后,在redis中会出现四条记录,这表示Session已经共享到了redis中。
我是在IDEA中进行测试的,把我的测试过程说一下:
- 启动第一个服务,端口号是8765,在8765这个服务上进行登陆操作。
- 启动第二个服务,端口号是8766,在8766上查询业务接口。
- 在8766这个服务上能得到正确响应。
不要担心,在本机IDEA上,把同一个服务启起来两个端口号,这跟分布式部署在本质上是一模一样的。