- 1 概述
- 2 原因
- 3 解决方案
- 4 方案实现
该篇基于《SpringSecurity in Action》三:Session并发控制问题 & 《SpringSecurity in Action》二: 基于redis实现session共享
在【《SpringSecurity in Action》三:Session并发控制问题】最后做测试时,刻意回避了一个问题,就是不要在多副本情况下进行测试,因为以上一篇的配置来说,还不支持在session共享下完成session并发控制。
这里要骚骚的涉及一些源码:
- session并发控制是在
org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
类中实现的 - 这个类依赖了一个接口叫
org.springframework.security.core.session.SessionRegistry
,该接口的默认实现是org.springframework.security.core.session.SessionRegistryImpl
- 该实现类维护了一个注册表,记录某一帐号进行的登陆信息:
org.springframework.security.core.session.SessionRegistryImpl#principals
,该注册表是一个map集合,注册表map的key是用户信息,即principal对象,value是sessionId的set集合 - 当进行登陆 *** 作时,SpringSecurity会检查这个map集合中某一用户对应的set集合长度有没有过超过
maximumSessions
,如果已经超过了,就会执行并发策略。 - 最后,
org.springframework.security.core.session.SessionRegistryImpl
这个实现类不是多副本共享的,是某个副本自己维护的
由于以上几个方面的原因,Session并发在多副本情况下是不生效的,除非每个副本都达到了我们配置的maximumSessions
,这时Session并发才会生效,但此时我们已经登陆了maximumSessions
* 副本数 次,很明显并不满足需求嘛。
既然问题出现在org.springframework.security.core.session.SessionRegistryImpl
这个实现类上,那我们就给org.springframework.security.core.session.SessionRegistry
接口换个实现类。
SpringSession给org.springframework.security.core.session.SessionRegistry
提供了一个实现类,叫org.springframework.session.security.SpringSessionBackedSessionRegistry
,该实现类依赖了一个Repository对象叫org.springframework.session.data.redis.RedisIndexedSessionRepository
,看名子可以猜到,现在去判断某一个帐号登陆了几次的时候,是通过redis中的共享session去完成的,这时在多副本情况下,就能准确地知道某一用户到底进行了几次登陆了。
这里又一次感受到了Spring的强大,这里也只需要几行代码就可以了。
在SpringSecurity配置类里引入依赖,并配置到HttpSecurity
里就OKAY了。
主要依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.0.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
dependencies>
配置文件
spring:
redis:
host: 256.73.82.27 # 地址,这个ip是瞎写的,第一位是256就不可能的对吧
port: 6379 # 端口
database: 1 # 数据库索引
password: redis
session:
store-type: redis
SpringSecurity配置类
@Autowired
private FindByIndexNameSessionRepository sessionRepository;
@Bean(name = "springSessionBackedSessionRegistry")
public SessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(sessionRepository);
}
/**
* 这个Bean的作业是在登出时,进行一个发布,具体做了什么还不是很清楚
* 如果没有该Bean,现象是如果配置了http.maxSessionsPreventsLogin(true),当用户登陆数量后,即使进行登出 *** 作,接下来也还是不能正常登陆。
*/
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
/*
* 省力其它配置信息
*/
.sessionManagement()
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
/*
* 省力其它配置信息
*/
将sessionRegistry配置为我们自己定义的SpringSessionBackedSessionRegistry。这时进行测试
- 在本机启两个端口的服务,一个端口是8765,一个端口是8766.
- 登陆请求A,向8765服务发起登陆请求。
- 登陆请求B,向8766服务发起登陆请求。
- 登陆请求C,再一次向8766服务发起登陆请求。
- 使用第一次登陆请求返回的session请求业务接口,发现认证失败,说明并发控制生效。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)