在SpringSecurity + SpringSession项目中如何实现当前在线用户的查询、剔除登录用户等操作

1、前言

  在前一篇《在SpringBoot项目中整合SpringSession,基于Redis实现对Session的管理和事件监听》笔记中,已经实践了在SpringBoot + SpringSecurity 项目中整合SpringSession,这里我们继续尝试如何统计当前在线用户,思路如下:通过统计当前所有未过期的Session信息,每个Session即对应一个登录用户(系统可以设置多端登录,所以可能存在一个登录用户对应多个Session情况,这里按照每个Session一个登录用户进行统计),同时在登录过程中,会把需要展示的用户信息,放到Session中,进而存到了Redis中,避免再次查询。

2、在线用户信息查询

2.1、查询所有在线用户信息

  在SpringSession中未找到查询全部Session的相关方法,所以这里采用了直接查询Redis数据的方法实现,同时为了保证Redis序列化与存储的时候一致,这里直接使用了RedisIndexedSessionRepository中的操作Redis的RedisOperations对象。具体实现如下:

/*** 查询当前在线的所有用户。通过查询Redis中所有未过期的Session信息实现。* 查询字段(username、nickname、userCode、hostAddress、lastAccessedTime、maxInactiveInterval、creationTime等)* @return*/public List<Map<String, Object>> queryOnlineUsers(){// 获取所有存储会话的键,并根据需要进行过滤和处理Set<Object> keys = sessionRepository.getSessionRedisOperations().keys("spring:session:sessions:*");List<Map<String,Object>> list =  new ArrayList<>();Object[] arr = {"lastAccessedTime","maxInactiveInterval","creationTime","sessionAttr:SPRING_SECURITY_CONTEXT"};keys.stream().forEach(item->{if(((String)item).indexOf("expires")==-1){//排除过期信息List<Object> values  = sessionRepository.getSessionRedisOperations().opsForHash().multiGet(item, Arrays.asList(arr));Map<String, Object> re = new HashMap<>();re.put("lastAccessedTime",values.get(0));re.put("maxInactiveInterval",values.get(1));re.put("creationTime",values.get(2));re.put("sessionId",parseUserToken((String)item));re.putAll(this.parseUserInfo(values.get(3)));list.add(re);}});return list;}
/*** 根据Redis中存储对象的Key解析对应的SessionId* @param key* @return*/private String parseUserToken(String key){if(StringUtils.isNotEmpty(key)){String[] arr = key.split(":");if(arr != null && arr.length == 4){return arr[3];}}return null;}/*** 解析Redis中存储的用户信息和登录信息* @param val* @return*/private Map<String, Object> parseUserInfo(Object val){Map<String, Object> userInfo = new HashMap<>();if(val != null && val instanceof SecurityContextImpl){JSONObject json = (JSONObject) JSONObject.toJSON(val);if(json != null && json.containsKey("authentication")){JSONObject authInfo = json.getJSONObject("authentication");if(authInfo != null){if(authInfo.containsKey("name")){//用户登录名称userInfo.put("username",authInfo.getString("name"));}if(authInfo.containsKey("details")){//用户登录时的地址JSONObject details = authInfo.getJSONObject("details");if(details != null && details.containsKey("remoteAddress")){userInfo.put("hostAddress",details.getString("remoteAddress"));}}if(authInfo.containsKey("principal")){JSONObject principal = authInfo.getJSONObject("principal");if(principal.containsKey("sysUser")){JSONObject sysUser = principal.getJSONObject("sysUser");userInfo.put("nickname",sysUser.getString("nickname"));userInfo.put("userCode",sysUser.getString("userCode"));}}}}}return userInfo;}

  在上述代码中,我们主要查询了“spring:session:sessions:*”中对应的数据,并过滤其中的“spring:session:sessions:expires”数据,然后在这里保存了四个可以值:

  1. creationTime Session创建时间
  2. lastAccessedTime 最后访问时间
  3. maxInactiveInterval Session的有效时长
  4. sessionAttr:SPRING_SECURITY_CONTEXT 默认存储了当前登录用户的认证信息

  上述用户的用户登录信息就是从上述四个参数中解析,其中用户登录的认证信息,一般只保存了默认的authentication信息,包括了details和principal信息,为了获取更多的用户信息,我会在登录时,将用户信息放到principal信息中。

在这里插入图片描述

2.2、填充登录用户信息

  填充用户登录信息,是在SpringSecurity的认证相关逻辑中实现的,方式有很多,这里选择了一种比较简单的方式,即在重写的UserDetailsService实现类的loadUserByUsername()方法中实现,同时,我们还需要构建一个UserDetails实现类,用于存储额外的登录用户信息,代码如下:

//UserDetails实现类,添加一个了sysUser字段,用于存储额外的用户信息
//这里的User 是org.springframework.security.core.userdetails.User
public class LoginUser extends User {private SysUserEntity sysUser;public LoginUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {super(username, password, authorities);}public SysUserEntity getSysUser() {return sysUser;}public void setSysUser(SysUserEntity sysUser) {this.sysUser = sysUser;}
}
//UserDetailsService实现类的重写loadUserByUsername()方法的逻辑如下:
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {Collection<GrantedAuthority> authorities = new ArrayList<>();SysUserEntity param = new SysUserEntity();param.setUsername(username);SysUserEntity user = sysUserService.getOne(param,true);if(user == null) {logger.info("用户名不存在,用户名:" + username);throw new UsernameNotFoundException("用户名不存在");}LoginUser loginUser = new LoginUser(user.getUsername(), user.getPassword(),authorities);loginUser.setSysUser(user);return loginUser;}

  至此,我们就完成了用户额外信息的填充,在上述获取用户认证信息sessionAttr:SPRING_SECURITY_CONTEXT对应数据时,内容如下:

{"data": [{"lastAccessedTime": 1694676717338,"maxInactiveInterval": 1800,"creationTime": 1694676716358,"sessionAttr:SPRING_SECURITY_CONTEXT": {"authentication": {"authenticated": true,"authorities": [],"details": {"remoteAddress": "192.168.1.87"},"name": "test","principal": {"accountNonExpired": true,"accountNonLocked": true,"authorities": [],"credentialsNonExpired": true,"enabled": true,"sysUser": {"nickname": "测试","roleCode": "test","roleName": "测试","userCode": "test_1693296199148","username": "test"},"username": "test"}}}}]
}

3、剔除在线用户(是一个用户的Token失效)

  根据sessionId使一个Session实现的方法,可以借助SpringSession的RedisIndexedSessionRepository方法中deleteById()方法实现,具体实现如下:

  /*** 通过SessionId使一个Session失效* @param sessionId* @return*/public boolean expireSession(String sessionId){sessionRepository.deleteById(sessionId);return true;}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spatial-Temporal Action Localization(三)】论文阅读2018年

文章目录 1. AVA: A Video Dataset of Spatio-temporally Localized Atomic Visual Actions 时空局部原子视觉动作的视频数据集摘要和结论模型框架思考不足之处时间信息对于识别 AVA 类别有多重要&#xff1f;定位与识别相比有何挑战性&#xff1f;哪些类别具有挑战性&#xff…

排序算法-----插入排序

目录 前言&#xff1a; 插入排序 原理图 代码实现 分析总结 二分法插入排序 代码实现 前言&#xff1a; 嗨嗨^_^&#xff0c;米娜桑&#xff0c;今天我们继续学习排序算法中的插入排序&#xff0c;激不激动&#xff0c;兴不兴奋呢&#xff01;好了废话不多说&#xff0c;…

制作ubuntu18.04系统盘

文章目录 前言一、下载ubuntu18.04的iso文件二、制作u盘系统盘2、使用ultra来制作系统盘2.1、加载iso2.2、制作系统盘 前言 安装ubuntu18.04系统 一、下载ubuntu18.04的iso文件 打开下面的网址&#xff0c;找到自己需要的iso文件 https://releases.ubuntu.com/二、制作u盘系…

国家网络安全周 | 金融日,一起 get金融行业数据安全

2023国家网络安全宣传周 热度一直在持续&#xff01; 9月15日是国家网络安全宣传金融日。 目前随着国际形势愈发严峻&#xff0c;金融机构基础设施的全面数字化升级&#xff0c;带来了全新的安全问题。数据安全不单是技术问题&#xff0c;更是已经成为一个关系社会稳定发展的…

开源库源码分析:Okhttp源码分析(一)

开源库源码分析&#xff1a;OkHttp源码分析 导言 接下来就要开始分析一些常用开源库的源码了&#xff0c;作为最常用的网络请求库&#xff0c;OkHttp以其强大的功能深受Android开发者的喜爱&#xff08;比如说我&#xff09;&#xff0c;还有对该库进行二次封装而成的热门库&a…

无涯教程-JavaScript - COLUMNS函数

描述 COLUMNS函数返回数组或引用中的列数。 语法 COLUMNS (array)争论 Argument描述Required/OptionalarrayAn array or array formula, or a reference to a range of cells for which you want the number of Columns.Required Notes COLUMNS(1:1)返回Excel中的列数,即…

20230912在ubuntu18.04下使用pigz来提高tar命令压缩解压缩的速度

20230912在ubuntu18.04下使用pigz来提高tar命令压缩解压缩的速度 2023/9/15 22:19 https://blog.csdn.net/wb4916/article/details/128447298 20221226编译Toybrick的TB-RK3588X开发板的Android12系统2-SDK预处理 4、单线程压缩。 建议使用&#xff1a;pigz多线程压缩&#xf…

极光笔记 | 推送服务数据中心选择:合规性与传输效率的双重考量

随着全球化进程的深入&#xff0c;跨境数据传输与存储问题已经变得愈发重要。推送服务的数据中心节点选择不仅关乎数据访问速度和用户体验&#xff0c;同时也直接牵扯到数据合规性和安全保障。EngageLab Push深知这一点&#xff0c;为了满足更多国际客户和全球用户触达需求&…

sqlserver存储过程报错:当前事务无法提交,而且无法支持写入日志文件的操作。请回滚该事务。

现象&#xff1a; 系统出现异常&#xff0c;手动执行过程提示如上。 问题排查&#xff1a; 1.直接执行的过程事务挂起&#xff08;排除&#xff09; 2.重启数据库实例&#xff08;重启后无效&#xff09; 3.过程中套用过程&#xff0c;套用的过程中使用事务&#xff0c;因为…

sql存储引擎

-- 查询建表语句 --可以查看引擎 show create table account; -- 可以看到默认引擎 InnoDB ENGINEInnoDB -- 查看当前数据库支持得存储引擎 show engines ; # InnoDB 默认 存储引擎 # MyISAM sql早期默认 存储引擎 # MEMORY 存储在内存中 用来做临时表和缓存 存储引擎 …

伺服取料机构控制

伺服运动控制系统相关介绍大家可以查看运动控制专栏&#xff0c;这篇文章主要讨论常用的伺服取料机构的控制问题。常用运动控制文章链接如下&#xff1a; EtherCAT总线运动控制学习笔记&#xff08;RXXW_Dor&#xff09;_ethercat控制字_RXXW_Dor的博客-CSDN博客说到总线控制&…

管理方法论:4. 一对一沟通——了解真实情况

团队人越多&#xff0c;管理者了解真实情况就越困难&#xff0c;为什么&#xff1f; 缺乏安静的、可以专注于沟通的环境。有些话不能跟太多人说&#xff0c;尤其是负面的想法和意见。有些人本来就内向、被动&#xff0c;其他人说话时&#xff0c;自己往往习惯于听&#xff0c;…