Springboot +spring security,实现session并发控制及实现原理分析

news2024/12/28 20:43:38

一.简介

在SpringSecurity中实现会话并发控制,只需要配置一个会话数量就可以了,先介绍下如何配置会话并发控制,然后再。介绍下SpringSecurity 如何实现会话并发控制。

二.创建项目

如何创建一个SpringSecurity项目,前面文章已经有说明了,这里就不重复写了。

三.代码实现

3.1设置只有一个会话

SecurityConfig 类,代码如下:

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .sessionManagement()
                .maximumSessions(1);
        return http.build();
    }
}

登陆一个客户端

在这里插入图片描述
登陆第二个客户端
在这里插入图片描述
刷新第一个客户端
在这里插入图片描述
这时候发现已经被挤掉了。

目前默认策略是:后来的会把前面的给挤掉,现在我们通过配置,禁止第二个客户端登陆

3.2禁止第二个客户端登陆

SecurityConfig 类,代码如下:

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true);
        return http.build();
    }
}

登陆第一个客户端
在这里插入图片描述
登陆第二个客户端
在这里插入图片描述

四.实现原理分析

session默认的过滤器是SessionManagementConfigurer

4.1SessionManagementConfigurer

点击.sessionManagement()进去,找到SessionManagementConfigurer,点进去看下主要是看init和configure方法。

4.1.1 init()方法

  1. 创建SecurityContextRepository
  2. 初始化 SessionAuthenticationStrategy,并添加到容器
    ConcurrentSessionControlAuthenticationStrategy
    defaultSessionAuthenticationStrategy
    RegisterSessionAuthenticationStrategy
    setMaximumSessions
    setExceptionIfMaximumExceeded = maxSessionsPreventsLogin
    CompositeSessionAuthenticationStrategy
    InvalidSessionStrategy

4.1.2 configure()方法

  1. 初始化 SessionManagementFilter
  2. 添加sessionManagementFilter 到http链中
  3. isConcurrentSessionControlEnabled &&添加ConcurrentSessionFilter 到http链中
  4. !this.enableSessionUrlRewriting && 添加 DisableEncodeUrlFilter
  5. SessionCreationPolicy.ALWAYS && 添加 ForceEagerSessionCreationFilter

4.2CompositeSessionAuthenticationStrategy

CompositeSessionAuthenticationStrategy是一个代理策略,它里面会包含很多SessionAuthenticationStrategy,主要有ConcurrentSessionControlAuthenticationStrategy和RegisterSessionAuthenticationStrategy。

4.3RegisterSessionAuthenticationStrategy

处理并发登录人数的数量,代码如下:

 public void onAuthentication(Authentication authentication, HttpServletRequest request,
   HttpServletResponse response) {
  this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
 }

这里直接调用this.sessionRegistry.registerNewSession方法,代码如下:

public void registerNewSession(String sessionId, Object principal) {
  Assert.hasText(sessionId, "SessionId required as per interface contract");
  Assert.notNull(principal, "Principal required as per interface contract");
  if (getSessionInformation(sessionId) != null) {
   removeSessionInformation(sessionId);
  }
  this.sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
  this.principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
   if (sessionsUsedByPrincipal == null) {
    sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
   }
   sessionsUsedByPrincipal.add(sessionId);
   this.logger.trace(LogMessage.format("Sessions used by '%s' : %s", principal, sessionsUsedByPrincipal));
   return sessionsUsedByPrincipal;
  });
 }
  1. 根据sessionId查找session,如果有,则移除
  2. 创建新的SessionInformation,维护到sessionIds中
  3. 维护sessionId到principals中

4.4ConcurrentSessionControlAuthenticationStrategy

onAuthentication方法代码如下:

public void onAuthentication(Authentication authentication, HttpServletRequest request,
   HttpServletResponse response) {
  int allowedSessions = getMaximumSessionsForThisUser(authentication);
  if (allowedSessions == -1) {
   // We permit unlimited logins
   return;
  }
  List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
  int sessionCount = sessions.size();
  if (sessionCount < allowedSessions) {
   // They haven't got too many login sessions running at present
   return;
  }
  if (sessionCount == allowedSessions) {
   HttpSession session = request.getSession(false);
   if (session != null) {
    // Only permit it though if this request is associated with one of the
    // already registered sessions
    for (SessionInformation si : sessions) {
     if (si.getSessionId().equals(session.getId())) {
      return;
     }
    }
   }
   // If the session is null, a new one will be created by the parent class,
   // exceeding the allowed number
  }
  allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
 }
  1. 获取当前用户允许同时在线的数量,如果 == -1(没有限制)则跳过并发校验
  2. 获取当前用户的所有在线session数量,如果小于限制数量则返回
  3. 如果等于限制数量,则判断当前的sessionId是否已经在集合中,如果在,则返回
  4. 否则走allowableSessionsExceeded 校验

allowableSessionsExceeded方法代码如下:

 protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,
   SessionRegistry registry) throws SessionAuthenticationException {
  if (this.exceptionIfMaximumExceeded || (sessions == null)) {
   throw new SessionAuthenticationException(
     this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
       new Object[] { allowableSessions }, "Maximum sessions of {0} for this principal exceeded"));
  }
  // Determine least recently used sessions, and mark them for invalidation
  sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
  int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
  List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
  for (SessionInformation session : sessionsToBeExpired) {
   session.expireNow();
  }
 }
  1. 如果配置了 maxSessionsPreventsLogin,则直接抛出异常,禁止新用户登录,否则往下走
  2. 将当前用户的所有session按照最后访问时间排序
  3. 获取最大允许同时在线的数量,然后在集合中 top n,其余的全部设置过期
  4. expireNow();
    this.expired = true;

4.5SessionManagementFilter

代码如下:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
  if (!this.securityContextRepository.containsContext(request)) {
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
   if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {
    // The user has been authenticated during the current request, so call the
    // session strategy
    try {
     this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
    }
    catch (SessionAuthenticationException ex) {
     SecurityContextHolder.clearContext();
     this.failureHandler.onAuthenticationFailure(request, response, ex);
     return;
    }    this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
   }
   else {
    if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
     if (this.invalidSessionStrategy != null) {
     this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
      return;
     }
    }
   }
  }
  chain.doFilter(request, response);
 }
  1. 如果securityContextRepository 有Context信息
  2. 如果抛出异常,则进行异常处理,并清楚context信息
    获取authentication
    如果authentication 不为空,则调用sessionAuthenticationStrategy.onAuthentication
    如果为空,则调用invalidSessionStrategy的onInvalidSessionDetected方法

4.6ConcurrentSessionFilter

ConcurrentSessionFilter类,代码如下:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
  HttpSession session = request.getSession(false);
  if (session != null) {
   SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
   if (info != null) {
    if (info.isExpired()) {
     // Expired - abort processing
     this.logger.debug(LogMessage
       .of(() -> "Requested session ID " + request.getRequestedSessionId() + " has expired."));
     doLogout(request, response);
     this.sessionInformationExpiredStrategy
       .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
     return;
    }
    // Non-expired - update last request date/time
    this.sessionRegistry.refreshLastRequest(info.getSessionId());
   }
  }
  chain.doFilter(request, response);
 }

4.7AbstractAuthenticationProcessingFilter

这个过滤器也会调用sessionStrategy.onAuthentication,进行session维护,代码如下:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws IOException, ServletException {
  
   Authentication authenticationResult = attemptAuthentication(request, response);
   this.sessionStrategy.onAuthentication(authenticationResult, request, response);
 }

整体流程图,截图如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/566817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive ---- 函数

Hive ---- 函数 1. 函数简介2. 单行函数1. 算术运算函数2. 数值函数3. 字符串函数4. 日期函数5. 流程控制函数6. 集合函数7. 案例演示 3. 高级聚合函数案例演示 4. 炸裂函数1. 概述2. 案例演示 5. 窗口函数1. 概述2. 常用窗口函数3. 案例演示 6. 自定义函数7. 自定义UDF函数 1.…

Unity - 记一次非正规变体优化带来的兼容性导致部分手机卡死的问题

文章目录 问题但是我咨询过 公司中台TA大佬 - 2023.4.6然后咨询 unity 技术官方 - 2023.4.6再次遇到卡死 - 2023.5.24 解决方法具体华为真机上的 DEBUG 问题 在 2023.4.6 我们的 角色展示界面 就遇到了 华为手机&#xff0c;red mi note 11 的测试手机上的 后 2023.5.24 再次遇…

SSM框架学习之spring

Spring 以下是关于Spring Boot学习的一些文档和资源&#xff0c;希望对你有帮助&#xff1a; Spring Boot官方文档&#xff1a;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ Spring Boot中文文档&#xff1a;https://www.springcloud.cc/spring-bo…

Server - 高性能的 PyTorch 训练环境配置 (PyTorch3D 和 FairScale)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/130863537 PyTorch3D 是基于 PyTorch 的 3D 数据深度学习库&#xff0c;提供了高效、模块化和可微分的组件&#xff0c;以简化 3D 深度学…

龙讯旷腾作为首批单位入驻北京昇腾人工智能计算中心

2023中关村论坛系列活动—北京人工智能产业创新发展大会圆满落幕&#xff0c;围绕北京AI产业发展&#xff0c;政产学研用各界大咖汇聚京城&#xff0c;中国科协副主席束为、北京市副市长于英杰、中国工程院院士廖湘科出席大会。会上&#xff0c;北京市门头沟区政府联合中关村发…

Kubernetes基础操作

K8S基础操作 ✨✨✨✨✨✨✨✨✨这个基础操作一切都基于各位把k8s搭建好哦&#xff0c;搭建的时候请一定一定一定&#xff08;很重要&#xff09;&#xff0c;选定一个版本&#xff0c;能避免很多错&#xff0c;然后本章节就给大家介绍了k8s最基础的操作&#xff0c;有一些复杂…

基于GPTP时间同步(时钟同步服务器)技术助力智能驾驶应用

基于GPTP时间同步&#xff08;时钟同步服务器&#xff09;技术助力智能驾驶应用 基于GPTP时间同步&#xff08;时钟同步服务器&#xff09;技术助力智能驾驶应用 智能驾驶区域网关架构并未采用车载以太网总线进行连接&#xff0c;而是采用传统的 CAN 总线、FlexRay 或 MOST 总线…

解决若依出现Error: Cannot find module ‘@/views/xxx‘问题

问题描述&#xff1a; 若依 vue 版菜单点不开&#xff0c;报错&#xff1a;Error: Cannot find module ‘/views/xxx’ 。后台、vue前端启动都没问题。但是左侧菜单点不开&#xff0c;一直在加载中。 原因&#xff1a; 路由懒加载&#xff0c;webpack版本问题&#xff0c;we…

常见淘宝API文档接口使用攻略,一文搞定

探索淘宝数据的奥秘&#xff0c;淘宝是目前国内最大的B2C电商平台之一&#xff0c;每天都会产生海量的数据。借助淘宝API技术文档&#xff0c;我们可以轻松地获取到这些数据&#xff0c;从而为电商运营和数据分析提供有力支持。 1.什么是淘宝API&#xff1f; 淘宝API&#xf…

工作分配问题——算法设计与分析(C实现)

目录 一、问题描述 二、问题分析 三、代码展示 四、结果验证 一、问题描述 问题描述&#xff1a;设有n件工作分配给n个人。将工作i分配给第j个人所需要的费用为Cij。试设计一个算法&#xff0c;为每个人都分配1件不同的工作&#xff0c;并使总费用达到最小值 算法设计&a…

nodejs+vue+elementui大学生多媒体学习系统

前端技术&#xff1a;nodejsvueelementui 前端&#xff1a;HTML5,CSS3、JavaScript、VUE(1)课程学习(包括课程分类 课程目录 课程学习等相关操作&#xff09; (2)课程评价 (3)课程统计 (4)相关信息管理(包括基本信息 课程编辑 注册登录等相关操作) 1、 node_modules文件夹(有np…

day18 - 使用直方图提高图像对比度

本期将使用图像直方图的相关知识来提高图像对比度&#xff0c;对图像进行优化&#xff0c;从而提高图像清晰度。 完成本期内容&#xff0c;你可以&#xff1a; 了解图像直方图的定义和计算方法了解直方图均衡化的原理学会使用直方图均衡化优化图像 若要运行案例代码&#xf…

SpringBoot 结合 mybatis-plus 实现分页功能

一、分页的原理 要实现分页功能方法有很多&#xff0c;但最基本的实现原理都差不多&#xff0c;所以在实现功能之前要先把原理搞明白。正如删除有 “逻辑删除” 和 “物理删除" 之分&#xff0c;分页也有 “逻辑分页” 和 “物理分页”&#xff1b; 1、逻辑分页&…

WalMiner插件之xlog解析恢复使用教程

WalMiner插件主要有两个功能&#xff0c;在此记录一下第二个功能数据页挽回&#xff08;坏块修复&#xff09;&#xff0c;学习一下关于这块的使用方法&#xff0c;方便日后回顾。 1 环境搭建 创建WalMiner的extension create extension walminer;语句解析: 该句SQL功能是安…

Linux用户管理相关命令(全)

1、Linux用户(账号)管理 查询用户(账号)信息&#xff08;判断用户(账号)是否存在&#xff09; id account新增用户(账号) useradd account设置用户(账号)密码 方式1&#xff1a; passwd account 方式2&#xff1a; echo 123|passwd --stdin account; #密码为123删除用户(账…

CMS 8bit单片机C语言编写指南

0 Preface/Foreword 单片机包含两部分&#xff1a;程序内存&#xff08;Program memory space&#xff09;和数据存储器(Ram memory space)。 CMS单片机堆栈深度受限&#xff0c;随具体的芯片而固定。 1 CMS C程序框架及数据类型 1.1 源程序基本框架 Example: 1.2 CMS C中变…

【JS】获取 Headers 头部信息

一、应用场景 当我们请求一个接口的时候&#xff0c;会发现 res 里面包含一个 headers 响应头信息&#xff1a; fetch(url, {method: GET,headers: {content-type: application/json,X-Requested-With: XMLHttpRequest,},mode: cors,credentials: include,}).then(res > {…

【算法】字符串转int类型思路及代码

文章目录 题目分析思路完整代码 题目 给你一个字符串&#xff0c;如何这个字符串符合日常的整形的书写规范&#xff0c;那么我们需要写出将其转化为int类型的方法&#xff0c;并且不能使用Java提供的API&#xff0c;比如parseInt方法。 分析 这道题考察的其实就是parseInt的…

亚马逊测评:提升产品排名、权重和销量的秘诀

亚马逊是全球最大的在线零售平台&#xff0c;覆盖了世界各地主要国家和地区&#xff0c;而随着平台商家的不断增加&#xff0c;为了提高自身排名&#xff0c;很多卖家开始寻找人员为他们的店铺和产品进行有偿评价&#xff0c;从而催生了亚马逊测评行业 亚马逊测评&#xff0c;…

笔试强训 Day6

选择题 1.十进制变量i的值为100&#xff0c;那么八进制的变量i的值为&#xff08;&#xff09; A 146 B 148C 144 D 142 本题很简单&#xff1a;100除8&#xff0c;取余数&#xff0c;直到商为零&#xff0c;最后反向的串起余数即可 2.执行下面语句后的输出为&#xff08;&…