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

news2024/10/6 16:20:44

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()方法的逻辑如下:
@Override
    public 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.coloradmin.cn/o/1015417.html

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

相关文章

看好多人都在劝退学计算机,可是张雪峰又 推荐过计算机,所以计算机到底是什么样 的?

张雪峰高考四百多分&#xff0c;但是他现在就瞧不起400多分的学生。说难听点&#xff0c;六七百分的 热门专业随便报谁不会啊&#xff1f; 计算机专业全世界都是过剩的&#xff0c;今年桂林电子科技&#xff0c;以前还是华为的校招大学&#xff0c;今年 计算机2/3待业。这个世…

程序员兼职社区招募(内含技术指导)

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;大数据专业硕士在读&#xff0c;CSDN人工智能领域博客专家&#xff0c;阿里云专家博主&#xff0c;专注大数据与人工智能知识分享。公众号&#xff1a; GoAI的学习小屋&#xff0c;免费分享书籍、简历、导图等资料&#xff0c…

flex:1的大坑

一、问题描述 整个类名为roomList 的大盒子设置了flex为1&#xff0c;与它同级的其他盒子都已经设置了宽高&#xff0c;但roomList 依然被内容撑开了&#xff0c;没有自适应 .roomList { flex: 1; } 二、原因分析 roomList的整个高度溢出&#xff0c;对于包裹roomList的父盒子…

pycharm安装(windows)

一、下载及安装 1.下载进入PyCharm官方下载地址&#xff1a; https://www.jetbrains.com/pycharm/download/ 下拉一下&#xff0c;直接下载社区版就行&#xff0c;是免费的&#xff0c;功能足够用了。 2.安装 (1) 找到你下载PyCharm的路径&#xff0c;双击.exe文件进行安装…

每日一题~合并二叉树

题目链接&#xff1a;617. 合并二叉树 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 思路分析&#xff1a; 由图可知&#xff0c;当两个位置都有节点的时候&#xff0c;直接将两个节点的 val 相加就是结果&#xff0c;如果在一个位置两棵树只有一棵在此位置上…

Vim的基础操作

前言 本文将向您介绍关于vim的基础操作 基础操作 在讲配置之前&#xff0c;我们可以新建一个文件 .vimrc&#xff0c;并用vim打开在里面输入set nu 先给界面加上行数&#xff0c;然后shift &#xff1b;输入wq退出 默认打开&#xff1a;命令模式 在命令模式中&#xff1a…

06乐观锁与悲观锁

乐观锁与悲观锁 悲观锁: 悲观锁比较适合插入数据,简单粗暴但是性能一般 乐观锁: 比较适合更新数据, 性能好但是成功率低(多个线程同时执行时只有一个可以执行成功),还需要访问数据库造成数据库压力过大 模拟乐观锁实现流程 第一步: 数据库中增加商品表t_product并插入一条数…

【MySQL】基础SQL语句——表的操作

文章目录 一. 创建表二. 查看表结构三. 修改表3.1 修改表名或列名3.2 插入数据3.3 添加列3.4 修改列类型3.5 删除列 四. 删除表结束语 一. 创建表 create table table_name(field1 datatype,field2 datatype...) charset 字符集 collate 校验规则 engine 存储引擎; 创建表 fiel…

07通用枚举和表的代码生成器

通用枚举 通用枚举 如果表中的有些字段值是固定的例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来为属性赋值 需求: 在数据库表添加字段sex 第一步: 设置枚举类型,使用EnumValue注解将注解所标识的属性值存储到数据库中 // 枚举类型只要设置getter方法 Getter …

2023/09/15 qt day1

代码实现图形化界面 #include "denglu.h" #include "ui_denglu.h" #include <QDebug> #include <QIcon> #include <QLabel> #include <QLineEdit> #include <QPushButton> denglu::denglu(QWidget *parent): QMainWindow(p…

JavaScript-promise使用+状态

Promise 什么是PromisePromise对象就是异步操作的最终完成和失败的结果&#xff1b; Promise的基本使用&#xff1a; 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compati…

层次聚类分析

1、python语言 from scipy.cluster import hierarchy # 导入层次聚类算法 import matplotlib.pylab as plt import numpy as np# 生成示例数据 np.random.seed(0) data np.random.random((20,1))# 使用树状图找到最佳聚类数 Z hierarchy.linkage(data,methodweighted,metric…

时序预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络时间序列预测

时序预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络时间序列预测 目录 时序预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优…

面对突如其来的 GC 问题如何下手解决

今天我们主要从一个实战案例入手分析面对突如其来的 GC 问题该如何下手解决。 想要下手解决 GC 问题&#xff0c;我们首先需要掌握下面这三种问题。 如何使用 jstat 命令查看 JVM 的 GC 情况&#xff1f; 面对海量 GC 日志参数&#xff0c;如何快速抓住问题根源&#xff1f; …

保密技术基础--北交大实验靶场2

由于第2、第3章的内部章节较少&#xff0c;所以我将其的体验感受一起写在这篇文章当中。 2.1 保密专用网络知识学习 这一小节也是一节的理论知识学习&#xff0c;学习过后有一个小测试&#xff0c;和第一章一样&#xff0c;学的知识和给的题目可以说是毫无关系。这一节的知识更…

VBA技术资料1-182

MF系列VBA技术资料 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-04属于定…

04_kibana 安装和配置指南

04_kibana 安装和配置指南 概述安装配置可能报错环境变量配置 概述 这个是干啥的呢&#xff1f; 目前我理解就是数据的展示 我们安装的目的是 请求访问ES&#xff0c; 如果没有安装完全可以使用postman代替 不过这个调试的时候有提示比较好&#xff0c; 所以我就安装了 安装…

【射频电路基础】第二章-谐振功率放大器

本书所用版本为:《射频电路基础》第二版&#xff08;赵建勋 邓军 著&#xff09; 网课详情见b站:《射频电路基础&#xff08;高频电子线路&#xff09;》 本书的笔记以书本和手写笔记结合为主。 文章目录 第二章 谐振功率放大器1. 谐振功率放大器的工作原理2. 谐振功率放大器…

Android kotlin开源项目-功能标题目录

目录 一、BRVAH二、开源项目1、RV列表动效&#xff08;标题目录&#xff09;2、拖拽与侧滑&#xff08;标题目录&#xff09;3、数据库&#xff08;标题目录&#xff09;4、树形图(多级菜单)&#xff08;标题目录&#xff09;5、轮播图与头条&#xff08;标题目录&#xff09;6…