SpringBoot基于Redis+WebSocket 实现账号单设备登录.

引言

在现代应用中,一个账号在多个设备上的同时登录可能带来安全隐患。为了解决这个问题,许多应用实现了单设备登录,确保同一个用户只能在一个设备上登录。当用户在新的设备上登录时,旧设备会被强制下线。
本文将介绍如何使用 Spring Boot 和 Redis 来实现单设备登录功能。

效果图

在线访问地址: https://www.coderman.club/#/dashboard
在这里插入图片描述

思路

userId:xxx (被覆盖)
userId:yyy

  1. 用户登录时,新的 token 会覆盖 Redis 中的旧 token,确保每次登录都是最新的设备。
  2. 接口访问时,通过拦截器对 token 进行验证,确保同一时间只有一个有效会话。
  3. 如果 token 不匹配或过期,则拦截请求,返回未授权的响应。

代码实现

在这里插入图片描述

/*** 权限拦截器* @author coderman*/
@Aspect
@Component
@Order(value = AopConstant.AUTH_ASPECT_ORDER)
@Lazy(value = false)
@Slf4j
public class AuthAspect {/*** 白名单接口*/public static List<String> whiteListUrl = new ArrayList<>();/*** 资源url与功能关系*/public static Map<String, Set<Integer>> systemAllResourceMap = new HashMap<>();/*** 无需拦截的url且有登录信息*/public static List<String> unFilterHasLoginInfoUrl = new ArrayList<>();/*** 资源api*/@Resourceprivate RescService rescApi;/*** 用户api*/@Resourceprivate UserService userApi;/*** 是否单设备登录校验*/private static final boolean isOneDeviceLogin = true;@PostConstructpublic void init() {this.refreshSystemAllRescMap();}/*** 刷新系统资源*/public void refreshSystemAllRescMap() {systemAllResourceMap = this.rescApi.getSystemAllRescMap(null).getResult();}@Pointcut("(execution(* com.coderman..controller..*(..)))")public void pointcut() {}@Around("pointcut()")public Object around(ProceedingJoinPoint point) throws Throwable {Cache<String, AuthUserVO> tokenCache = CacheUtil.getInstance().getTokenCache();Cache<Integer, String> deviceCache = CacheUtil.getInstance().getDeviceCache();HttpServletRequest request = HttpContextUtil.getHttpServletRequest();String path = request.getServletPath();// 白名单直接放行if (whiteListUrl.contains(path)) {return point.proceed();}// 访问令牌String token = AuthUtil.getToken();if (StringUtils.isBlank(token)) {throw new BusinessException(ResultConstant.RESULT_CODE_401, "会话已过期, 请重新登录");}// 系统不存在的资源直接返回if (!systemAllResourceMap.containsKey(path) && !unFilterHasLoginInfoUrl.contains(path)) {throw new BusinessException(ResultConstant.RESULT_CODE_404, "您访问的接口不存在!");}// 用户信息AuthUserVO authUserVO = null;try {authUserVO = tokenCache.get(token, () -> {log.debug("尝试从redis中获取用户信息结果.token:{}", token);return userApi.getUserByToken(token);});} catch (Exception ignore) {}if (authUserVO == null || System.currentTimeMillis() > authUserVO.getExpiredTime()) {tokenCache.invalidate(token);throw new BusinessException(ResultConstant.RESULT_CODE_401, "会话已过期, 请重新登录");}// 单设备校验if (isOneDeviceLogin) {Integer userId = authUserVO.getUserId();String deviceToken = StringUtils.EMPTY;try {deviceToken = deviceCache.get(userId, () -> {log.debug("尝试从redis中获取设备信息结果.userId:{}", userId);return userApi.getTokenByUserId(userId);});} catch (Exception ignore) {}if (StringUtils.isNotBlank(deviceToken) && !StringUtils.equals(deviceToken, token)) {deviceCache.invalidate(userId);throw new BusinessException(ResultConstant.RESULT_CODE_401, "账号已在其他设备上登录!");}}// 不需要过滤的url且有登入信息,设置会话后直接放行if (unFilterHasLoginInfoUrl.contains(path)) {AuthUtil.setCurrent(authUserVO);return point.proceed();}// 验证用户权限List<Integer> myRescIds = authUserVO.getRescIdList();Set<Integer> rescIds = Sets.newHashSet();if (CollectionUtils.isNotEmpty(systemAllResourceMap.get(path))) {rescIds = new HashSet<>(systemAllResourceMap.get(path));}if (CollectionUtils.isNotEmpty(myRescIds)) {for (Integer rescId : rescIds) {if (myRescIds.contains(rescId)) {AuthUtil.setCurrent(authUserVO);return point.proceed();}}}throw new BusinessException(ResultConstant.RESULT_CODE_403, "接口无权限");}@RedisChannelListener(channelName = RedisConstant.CHANNEL_REFRESH_RESC)public void refreshRescListener(String msgContent) {log.warn("doRefreshResc start - > {}", msgContent);this.refreshSystemAllRescMap();log.warn("doRefreshResc end - > {}", msgContent);}@RedisChannelListener(channelName = RedisConstant.CHANNEL_REFRESH_SESSION_CACHE, clazz = AuthUserVO.class)public void refreshSessionCache(AuthUserVO logoutUser) {String token = logoutUser.getAccessToken();Integer userId = logoutUser.getUserId();log.warn("doUserLogout start - > {}", token);// 清除会话缓存Cache<String, AuthUserVO> tokenCache = CacheUtil.getInstance().getTokenCache();tokenCache.invalidate(token);// 清除设备缓存Cache<Integer, String> deviceCache = CacheUtil.getInstance().getDeviceCache();deviceCache.invalidate(userId);log.warn("doUserLogout end - > {}", token);}
}

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

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

相关文章

MVC配置文件及位置

配置文件位置 默认位置 WEB-INF目录下&#xff0c;文件名&#xff1a;<servlet-name>-servlet.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.…

【若依项目-RuoYi】掌握若依前端的基本流程

搞毕设项目&#xff0c;使用前后端分离技术&#xff0c;后端springBoot&#xff0c;前端vue3element plus。自己已经写好前端与后端代码&#xff0c;但想换一个前端界面所以使用到了若依&#xff0c;前前后后遇到许多坑&#xff0c;记录一下&#xff0c;方便之后能够快速回忆。…

图像边缘检测示例(综合利用阈值分割、数学形态学和边缘检测算子)

一、问题 读入一副灰度图像&#xff08;如果是彩色图像&#xff0c;可以先将其转化为灰度图像&#xff09;&#xff0c;然后提取比较理想的灰度图像边缘。这里以moon.tif为例。 二、算法 大家一开始容易想到直接利用MATLAB的内置函数edge并采用不同边缘提取算子进行边缘提取&a…

R语言的数据结构-向量

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言编程_夏天又到了的博客-CSDN博客 在R语言中&#xff0c;数据结构是非常关键的部分&#xff0c;它提…

集成方案 | Docusign + 泛微,实现全流程电子化签署!

本文将详细介绍 Docusign 与泛微的集成步骤及其效果&#xff0c;并通过实际应用场景来展示 Docusign 的强大集成能力&#xff0c;以证明 Docusign 集成功能的高效性和实用性。 在现代企业运营中&#xff0c;效率和合规性是至关重要的。泛微作为企业级办公自动化和流程管理的解决…

Docker Compose应用实战

文章目录 1、使用Docker Compose必要性及定义2、Docker Compose应用参考资料3、Docker Compose应用最佳实践步骤1_概念2_步骤 4、Docker Compose安装5、Docker Compose应用案例1_网站文件准备2_Dockerfile文件准备3_Compose文件准备4_使用docker-compose up启动容器5_访问6_常见…

51c大模型~合集88

我自己的原文哦~ https://blog.51cto.com/whaosoft/12805165 #Number Cookbook 数字比你想得更复杂——一文带你了解大模型数字处理能力的方方面面 目前大语言模型&#xff08;Large Language Models, LLMs&#xff09;的推理能力备受关注。从思维链&#xff08;Chain of…

STP(生成树协议)

STP的基本概念 概述 STP是一个用于局域网中消除环路的协议。运行该协议的设备通过彼此交互信息而发现网络中的环路&#xff0c;并对某些接口进行阻塞以消除环路。STP在网络中运行后会持续监控网络的状态&#xff0c;当网络出现拓扑变更时&#xff0c;STP能够感知并且进行自动…

GLM-4-Plus初体验

引言&#xff1a;为什么高效的内容创作如此重要&#xff1f; 在当前竞争激烈的市场环境中&#xff0c;内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子&#xff0c;还是制作广告&#xff0c;优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…

Jetpack Compose赋能:以速破局,高效打造非凡应用

Android Compose 是谷歌推出的一种现代化 UI 框架&#xff0c;基于 Kotlin 编程语言&#xff0c;旨在简化和加速 Android 应用开发。它以声明式编程为核心&#xff0c;与传统的 View 系统相比&#xff0c;Compose 提供了更直观、更简洁的开发体验。以下是对 Android Compose 的…

MinerU:PDF文档提取工具

目录 docker一键启动本地配置下载模型权重文件demo.py使用命令行启动GPU使用情况 wget https://github.com/opendatalab/MinerU/raw/master/Dockerfile docker build -t mineru:latest .docker一键启动 有点问题&#xff0c;晚点更新 本地配置 就是在Python环境中配置依赖和…

UE4_控件蓝图_制作3D生命血条

一&#xff1a;效果图如下&#xff1a; 二、实现步骤&#xff1a; 1、新建敌人 右键蓝图类 选择角色&#xff0c; 重命名为BP_Enemytest。 双击打开&#xff0c;配置敌人网格体 修改位置及朝向 效果如下&#xff1a; 选择合适的动画蓝图类&#xff1a; 人物就有了动作&#x…

【深度学习】深刻理解ViT

ViT&#xff08;Vision Transformer&#xff09;是谷歌研究团队于2020年提出的一种新型图像识别模型&#xff0c;首次将Transformer架构成功应用于计算机视觉任务中。Transformer最初应用于自然语言处理&#xff08;如BERT和GPT&#xff09;&#xff0c;而ViT展示了其在视觉任务…

用于日语词汇学习的微信小程序+ssm

日语词汇学习小程序是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。本学生所在学院多采用半手工管理日语词汇学习小程序的方式&#xff0c;所以有必要开发日语词汇…

ichunqiu-2024年春秋杯网络安全联赛夏季赛-brother

1.打开题目&#xff0c;看到题目我就想到了再后面加一个ls&#xff0c;结果回显了ls&#xff0c;然后又想到会不会是模板注入&#xff0c;尝试{{7*7}}&#xff0c;然后页面返回了49&#xff0c;说明存在模板注入 如下&#xff0c;判定为模板注入 看一下系统环境配置 然后看可…

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 数据处理

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 数据处理 flyfish 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_LoRA配置如何写 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_单图推理 基于Qwen2-VL模型针对LaTeX_OCR任务进行微调训练_-_原模型_单图推理 基于Q…

QT从入门到精通——Qlabel介绍与使用

1. QT介绍——代码测试 Qt 是一个跨平台的应用程序开发框架&#xff0c;广泛用于开发图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;也支持非图形应用程序的开发。Qt 提供了一套工具和库&#xff0c;使得开发者能够高效地构建高性能、可移植的应用程序。以下是…

Edge SCDN深度解析,边缘安全加速的创新实践

边缘安全加速&#xff08;Edge Secure Content Delivery Network&#xff0c;SCDN&#xff09;是酷盾安全推出的边缘集分布式 DDoS 防护、CC 防护、WAF 防护、BOT 行为分析为一体的安全加速解决方案。通过边缘缓存技术&#xff0c;智能调度使用户就近获取所需内容&#xff0c;为…

WebRTC Simulcast 大小流介绍与优化实践

Simulcast 是 WebRTC 中的一种标准化技术 &#xff0c;简称大小流。通过 Simulcast&#xff0c;客户端可以同时发送同一视频的多个版本。每个版本都以不同的分辨率和帧率独立编码&#xff0c;带宽较多的拉流端可以接收较高质量的视频流&#xff0c;带宽有限的拉流端则可以接收较…

40分钟学 Go 语言高并发:服务监控与追踪

服务监控与追踪 一、知识要点总览 模块核心内容技术选型难度监控指标请求量、响应时间、错误率、资源使用Prometheus Grafana中链路追踪分布式调用链、性能瓶颈分析Jaeger, OpenTelemetry高日志处理日志收集、分析、存储ELK Stack中告警系统告警规则、通知渠道、告警分级Ale…