《SpringSecurity in Action》四:Session共享下的Session并发控制问题

《SpringSecurity in Action》四:Session共享下的Session并发控制问题,第1张

文章目录
    • 1 概述
    • 2 原因
    • 3 解决方案
    • 4 方案实现

1 概述

该篇基于《SpringSecurity in Action》三:Session并发控制问题 & 《SpringSecurity in Action》二: 基于redis实现session共享
在【《SpringSecurity in Action》三:Session并发控制问题】最后做测试时,刻意回避了一个问题,就是不要在多副本情况下进行测试,因为以上一篇的配置来说,还不支持在session共享下完成session并发控制。

2 原因

这里要骚骚的涉及一些源码:

  • 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 * 副本数 次,很明显并不满足需求嘛。

3 解决方案

既然问题出现在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去完成的,这时在多副本情况下,就能准确地知道某一用户到底进行了几次登陆了。

4 方案实现

这里又一次感受到了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请求业务接口,发现认证失败,说明并发控制生效。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/787657.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存