SpringBoot 控制并发登录的人数教程

SpringBoot 并发登录人数控制 通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 本文借鉴了 https://jinnianshilongnian.iteye.com/blog/2039760 如果你是使用 Shiro + Session 的模式,可以阅读此文 demo 技术... 阅读详情

作者:殷天文 

来源: www.jianshu.com/p/b6f5ec98d790

通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能

本文借鉴了 https://jinnianshilongnian.iteye.com/blog/2039760

如果你是使用 Shiro + Session 的模式,可以阅读此文

demo 技术选型

  • SpringBoot

  • JWT

  • Filter

  • Redis + Redisson


JWT(token)存储在Redis中,类似 JSessionId-Session的关系,用户登录后每次请求在Header中携带jwt
如果你是使用session的话,也完全可以借鉴本文的思路,只是代码上需要加些改动

两种实现思路

比较时间戳

维护一个 username: jwtToken 这样的一个 key-value 在Reids中, Filter逻辑如下

图片不清可点开放大

public class CompareKickOutFilter extends KickOutFilter {

    @Autowired
    private UserService userService;

    @Override
    public boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader("Authorization");
        String username = JWTUtil.getUsername(token);
        String userKey = PREFIX + username;

        RBucket<String> bucket = redissonClient.getBucket(userKey);
        String redisToken = bucket.get();

        if (token.equals(redisToken)) {
            return true;

        } else if (StringUtils.isBlank(redisToken)) {
            bucket.set(token);

        } else {
            Long redisTokenUnixTime = JWTUtil.getClaim(redisToken, "createTime").asLong();
            Long tokenUnixTime = JWTUtil.getClaim(token, "createTime").asLong();

            // token > redisToken 则覆盖
            if (tokenUnixTime.compareTo(redisTokenUnixTime) > 0) {
                bucket.set(token);

            } else {
                // 注销当前token
                userService.logout(token);
                sendJsonResponse(response, 4001, "您的账号已在其他设备登录");
                return false;

            }

        }

        return true;

    }
}

队列踢出

public class QueueKickOutFilter extends KickOutFilter {
    /**
     * 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
     */
    private boolean kickoutAfter = false;
    /**
     * 同一个帐号最大会话数 默认1
     */
    private int maxSession = 1;

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    @Override
    public boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String token = request.getHeader("Authorization");
        UserBO currentSession = CurrentUser.get();
        Assert.notNull(currentSession, "currentSession cannot null");
        String username = currentSession.getUsername();
        String userKey = PREFIX + "deque_" + username;
        String lockKey = PREFIX_LOCK + username;

        RLock lock = redissonClient.getLock(lockKey);

        lock.lock(2, TimeUnit.SECONDS);

        try {
            RDeque<String> deque = redissonClient.getDeque(userKey);

            // 如果队列里没有此token,且用户没有被踢出;放入队列
            if (!deque.contains(token) && currentSession.isKickout() == false) {
                deque.push(token);
            }

            // 如果队列里的sessionId数超出最大会话数,开始踢人
            while (deque.size() > maxSession) {
                String kickoutSessionId;
                if (kickoutAfter) { // 如果踢出后者
                    kickoutSessionId = deque.removeFirst();
                } else { // 否则踢出前者
                    kickoutSessionId = deque.removeLast();
                }

                try {
                    RBucket<UserBO> bucket = redissonClient.getBucket(kickoutSessionId);
                    UserBO kickoutSession = bucket.get();

                    if (kickoutSession != null) {
                        // 设置会话的kickout属性表示踢出了
                        kickoutSession.setKickout(true);
                        bucket.set(kickoutSession);
                    }

                } catch (Exception e) {
                }

            }

            // 如果被踢出了,直接退出,重定向到踢出后的地址
            if (currentSession.isKickout()) {
                // 会话被踢出了
                try {
                    // 注销
                    userService.logout(token);
                    sendJsonResponse(response, 4001, "您的账号已在其他设备登录");

                } catch (Exception e) {
                }

                return false;

            }

        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                LOGGER.info(Thread.currentThread().getName() + " unlock");

            } else {
                LOGGER.info(Thread.currentThread().getName() + " already automatically release lock");
            }
        }

        return true;
    }

}


比较两种方法

第一种方法逻辑简单粗暴, 只维护一个key-value 不需要使用锁,非要说缺点的话没有第二种方法灵活。

第二种方法我很喜欢,代码很优雅灵活,但是逻辑相对麻烦一些,而且为了保证线程安全地操作队列,要使用分布式锁。目前我们项目中使用的是第一种方法

演示

下载地址:

https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/login-control

  • 运行项目,访问localhost:8887 demo中没有存储用户信息,随意输入用户名密码,用户名相同则被踢出

  • 访问 localhost:8887/index.html 弹出用户信息, 代表当前用户有效

  • 另一个浏览器登录相同用户名,回到第一个浏览器刷新页面,提示被踢出

  • application.properties中选择开启哪种过滤器模式,默认是比较时间戳踢出,开启队列踢出 queue-filter.enabled=true

springboot集成shiro禁止用户多点登录或重复登录剔除 近期用springboot整合shiro,进行了一个小项目。因为shiro只是一个Java安全框架,并不会做重复登录的拦截,当一个用户登录成功后(chrome登录),再用另外一个浏览器登录(IE登录)或者另外一台电脑进行登录,两个都会登录成功,两个不同的session。(题外话:同一个浏览器登录后,再打开一个标签页访问项目会直接进入登录后跳转的页面,两者是同一个session。) 预期目的:不允... 阅读详情

相关推荐

计算机毕业设计springboot理发店线上预约排队的设计与实现7vsz2【附源码】

选题背景: 随着互联网的快速发展和智能手机的普及,线上预约已经成为人们生活中常见的一种方式。而在繁忙的都市生活中,理发店作为一个常去的地方,也面临着排队等候时间过长、效率低下的问题。因此,设计和实现一个理发店线上预约排队系统具有重要的意义。 意义: 首先,理发店线上预约排队系统可以提高用户的使用体验。传统的理发店排队方式往往需要用户到店后才能进行排队,等待时间不确定,容易造成用户的不便和浪费时间。而通过线上预约排队系统,用户可以提前选择合适的时间段进行预约,避免了长时间的等待,提高了用户的满意度。 其次

sheji1306的博客 578

微服务Springboot实战大揭秘/高并发/高可用/高负载/互联网技术

Java架构师系列课程是针对有志向架构师发展的广大学员而设置,不管你是工作一到三年,还是三到五年,本课程都会对你产生巨大的提升,让你的薪资大幅翻倍,走向BAT架构师的伟大梦想。

Spring Boot 接口并发限制实现(Java)

在开发和设计Web应用程序时,我们通常需要考虑接口的并发访问控制,以防止过多的请求同时进入服务器,导致性能下降甚至系统崩溃。通过自定义注解和拦截器的结合,我们可以轻松地对需要进行并发控制的接口进行限制。当然,上述代码只是一个简单的实现示例,实际应用中可能还需要考虑更多的细节和场景,例如并发限制的粒度、并发数的动态调整等。在Spring Boot应用程序的配置类中,我们需要将自定义的并发限制拦截器添加到Spring MVC的拦截器链中。注解标注,并设置最大并发数为10。注解的接口方法,并进行并发限制的处理。

TechWhiz的博客 870

springboot请求排队处理

springboot 请求排队处理 阻塞队列+线程池

weixin_41115084的博客 3247

告别信号毛刺!手把手教你用ADS和IBIS-AMI模型搞定PCIe Gen3眼图仿真

本文详细介绍了如何使用Keysight ADS和IBIS-AMI模型进行PCIe Gen3眼图仿真,解决高速信号传输中的信号质量问题。从仿真环境搭建、S参数预处理到眼图优化与故障排除,提供了一套完整的工程实践指南,帮助工程师提升PCIe Gen3接口的信号完整性。

weixin_28366053的博客 371

Argoverse2数据集保姆级导入教程:从下载到预处理全流程解析

本文详细解析了Argoverse2数据集的完整导入流程,从环境配置、数据下载到预处理和高效加载策略。作为自动驾驶领域的标杆数据集,Argoverse2提供多模态传感器数据和高精地图信息,帮助研究者快速搭建数据处理流水线。文章包含实用代码示例和性能优化技巧,特别适合自动驾驶算法开发者参考。

weixin_29035405的博客 338

SpringBoot 实现并发登录人数控制

作者丨殷天文www.jianshu.com/p/b6f5ec98d790今天跟大家分享SpringBoot 实现并发登录人数控制的知识。1SpringBoot 实现并发登录人数控制通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能...

独行侠梦的博客 1166

别再为域名备案发愁了!微信小程序后端调用,试试这个‘官方捷径’——云托管

本文介绍了微信小程序后端部署的新思路——利用微信云托管服务绕过域名备案难题。通过详细的环境准备、数据库配置、服务部署和小程序端调用方案,帮助开发者快速实现小程序与后端服务的无缝对接,大幅缩短上线周期并降低运维成本。

weixin_29243279的博客 679

springboot + shiro 尝试登录次数限制与并发登录人数控制

源码项目地址 尝试登录次数控制实现 实现原理 Realm在验证用户身份的时候,要进行密码匹配。最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成的。我们在这里继承这个接口,自定义一个密码匹配器,缓存入键值对用户名以及匹配次数,若通过密码匹配,则删除该键值对,若不匹配则匹配次数自增。超过给定的次数限制则抛出错误。这里缓存用的是ehcac...

weixin_33775572的博客 2028

别再搞混了!Vue3里xgplayer播放FLV视频与FLV直播流,配置到底差在哪?

本文详细解析了在Vue3项目中使用xgplayer播放FLV视频与FLV直播流的配置差异,帮助开发者避免常见错误。通过对比点播与直播的核心协议差异、插件选择及配置参数,提供实战代码示例和性能优化技巧,确保流畅播放体验。特别针对直播流场景,深入讲解专属配置和问题排查方法。

weixin_28687331的博客 150

【Codex】Codex安装+DeepSeek API接入;无需登录,手把手带你使用codex

好多人使用codex时都卡在验证码登录那一步,今天就来介绍一种方法,不需要登录ChatGPT的账号,自然也就不需要手机验证码了;接入DeepSeek的api,一样可以丝滑使用codex;记录从零开始安装使用codex以及DeepSeek API接入的整个过程。不需要登录openAI;

AnChenliang_1002的博客 3550

springboot整合shiro-在线人数以及并发登录人数控制(七)

原文地址,转载请注明出处:&amp;amp;nbsp;https://blog.youkuaiyun.com/qq_34021712/article/details/80457041&amp;amp;nbsp;&amp;amp;nbsp; &amp;amp;nbsp;&amp;amp;nbsp;©王赛超&amp;amp;nbsp; 项目中有时候会遇到统计当前在线人数的需求,也有这种情况当A 用户在邯郸地区登录 ,然后A用户在北京地区再登录 ,要踢出邯郸登录的状态。如果用

这个名字想了很久 2万+

基于YOLOv11的建筑图纸物体检测系统设计与实现

包含多种建筑平面图,标注了墙体、门窗等元素:CAD格式的建筑图纸数据集:专门针对建筑元素的标注数据集自定义数据集:使用LabelImg等工具自行标注的建筑图纸本文以FloorPlanDataset为基础,结合部分自定义标注数据构建混合数据集。wall (墙体)door (门)window (窗)stair (楼梯)furniture (家具)text (文字标注)dimension (尺寸标注)column (柱子)

YOLO 537

SpringBoot整合Shiro-在线人数以及并发登录人数控制(七)

原文:https://blog.youkuaiyun.com/qq_34021712/article/details/80457041 项目中有时候会遇到统计当前在线人数的需求,也有这种情况当A 用户在邯郸地区登录 ,然后A用户在北京地区再登录 ,要踢出邯郸登录的状态。如果用户在北京重新登录,那么又要踢出邯郸的用户,这样反复。 这样保证了一个帐号只能同时一个人使用。那么下面来讲解一下 Shiro 怎么实现在线人数统计 以及 并发人数控制这个功能。 并发人数控制 参考开涛大神博客:http://jinnianshilon

Doge的技术小屋 858

ICC2(一)import design —— tluplus

itf转tluplus相关命令 TLUPlus是存储RC系数的二进制表格式。 TLUPlus模型通过包括宽度,空间,密度和温度对电阻系数的影响,可以估算RC计算net delay。 1.2 产生TLUplus的方法 Itf文件转TLUplus的命令: grdgenxo -itf2TLUPlus -i xx.itf-o xx.tlu+ itf: interconnect technology format file 含每层的厚度,面积等参数 itf同样可以转nxtgrd格式文...

m0_61544122的博客 5486

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Spring Boot 并发登录人数控制

Spring Boot 并发登录人数控制简介实现Demo 技术选型两种实现思路比较时间戳队列踢出比较两种方法演示 简介 通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 实现 Demo 技术选型 SpringBoot JWT Filter Redis +...

m0_37540783的博客 232

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

springboot+shiro实现登录人数控制

今天又是美好的一天,好了,不多逼逼。今天内容有点多,咱们抓紧时间。 好,咱们来讲讲如何写springboot+shiro 实现登陆人数控制 来,咱们直接看代码 conrtoller:咱们就是一个正常登陆,没什么区别 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password); System.ou

weixin_45541795的博客 996

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

SpringBoot如何控制并发登录人数

阅读文本大概需要3分钟。作者:殷天文www.jianshu.com/p/b6f5ec98d790技术经验交流: ,本篇示例代码放文末。通常系统都会限制同一个账号的登录人...

天行健,君子以自强不息;地势坤,君子以厚德载物。 245
上一篇: 两万字深度介绍分布式系统原理!【收藏了~】
下一篇: 面试官:什么是NIO?NIO的原理是什么机制?
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值