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

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;
} }

比较两种方法

  1. 第一种方法逻辑简单粗暴, 只维护一个key-value 不需要使用锁,非要说缺点的话没有第二种方法灵活。
  2. 第二种方法我很喜欢,代码很优雅灵活,但是逻辑相对麻烦一些,而且为了保证线程安全地操作队列,要使用分布式锁。目前我们项目中使用的是第一种方法

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ

SpringBoot 并发登录人数控制的更多相关文章

  1. 2017.4.12 开涛shiro教程-第十八章-并发登录人数控制

    原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 开涛shiro教程-第十八章-并发登录人数控制 shiro中没有提 ...

  2. 第十八章 并发登录人数控制——《跟我学Shiro》

    目录贴:跟我学Shiro目录贴 在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录:要么踢出前者登录(强制退出).比如spring securi ...

  3. 2017.6.30 用shiro实现并发登录人数控制(实际项目中的实现)

    之前的学习总结:http://www.cnblogs.com/lyh421/p/6698871.html 1.kickout功能描述 如果将配置文件中的kickout设置为true,则在另处再次登录时 ...

  4. 并发登录人数控制--Shiro系列(二)

    为了安全起见,同一个账号理应同时只能在一台设备上登录,后面登录的踢出前面登录的.用Shiro可以轻松实现此功能. shiro中sessionManager是专门作会话管理的,而sessinManage ...

  5. Windows登录脚本可以限制并发登录吗

    在Windows服务器中,使用一个Windows登录脚本来限制并发会话靠谱吗? 事实上,这种解决方案存在很多缺点和弱点,并不能满足大中型IT基础设施的安全性需求. 一.使用登陆脚本限制并发会话,恶意用 ...

  6. JAVA之旅(三十三)——TCP传输,互相(伤害)传输,复制文件,上传图片,多并发上传,多并发登录

    JAVA之旅(三十三)--TCP传输,互相(伤害)传输,复制文件,上传图片,多并发上传,多并发登录 我们继续网络编程 一.TCP 说完UDP,我们就来说下我们应该重点掌握的TCP了 TCP传输 Soc ...

  7. SpringBoot注册登录(三):注册--验证账号密码是否符合格式及后台完成注册功能

    SpringBoot注册登录(一):User表的设计点击打开链接SpringBoot注册登录(二):注册---验证码kaptcha的实现点击打开链接      SpringBoot注册登录(三):注册 ...

  8. 063 日志分析(pv  uv  登录人数  游客人数  平均访问时间  二跳率  独立IP)

    1.需求分析 分析指标 pv uv 登录人数 游客人数 平均访问时间 二跳率 独立IP 2.使用的日志(一号店),会话信息 3.创建数据库 4.创建源表,存储源数据 5.创建我们需要的use表 6.创 ...

  9. springboot之登录注册

    springboot之登录注册 目录结构 pom.xml <?xml version="1.0" encoding="UTF-8"?> <pr ...

随机推荐

  1. 在vue中使用基于d3为基础的dagre-d3.js搞定一个流程图组件

    项目中想搞定一个流程图,开始使用了阿里的G6,但是G6目前不支持手势,这样就很郁闷了,因为公司的领导都是使用iPad看的,你不支持手势是不行的,后来又想到了百度的echarts,试了试,感觉还不错,手 ...

  2. SpringBoot 发送邮件功能实现

    背景 有个小伙伴问我你以前发邮件功能怎么弄的.然后我就给他找了个demo,正好在此也写一下,分享给大家. 理清痛点 发送邮件,大家可以想一下,坑的地方在哪? 我觉得是三个吧. 第一:邮件白名单问题. ...

  3. 使用Python编写打字训练小程序

    你眼中的程序猿 别人眼中的程序猿,是什么样子?打字如飞,各种炫酷的页面切换,一个个好似黑客般的网站破解.可现实呢? 二指禅的敲键盘,写一行代码,查半天百度-那么如何能让我们从外表上变得更像一个程序猿呢 ...

  4. Docker数据挂载

    Docker数据管理 在容器中管理数据主要有两种方式: 数据卷(Volumes) 挂载主机目录(Bind mounts) 数据卷 数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很 ...

  5. php上传下载文件

    之前做一个上传下载的项目,发现网上的和自己需求不是很一样,翻阅了下书籍和整理了下网上的一些代码.做了一个上传下载的demo,上传通过php本身的uploadfile函数,并返回以时间戳命名的文件名后, ...

  6. 批量修改bilibili客户端下载视频文件名

    代码已上传:Github 起因 昨天晚上从B站电脑客户端下了一个分集视频 但是下载后的视频是这样的: 视频名是这样的: 这样既不直观又不美观,就算把视频文件放到一个文件夹内,连续看视频时也不容易记住看 ...

  7. imagenet-vgg-verydeep-19.mat格式详解

    .mat是matlab生成的文件.用matlab打开文件imagenet-vgg-verydeep-19.mat可以帮助理解其结构.matlab代码如下: a = open('D:\imagenet- ...

  8. 基于iCamera测试高清摄像头OV7725小结

    基于iCamera测试高清摄像头OV7725小结 先看看硬件特点 然后看看硬件测试,usb采集出图 默认是不带晶振的,可以通过usb提供提供12M.24M.48M时钟 软件出图 可以通过修改0x11, ...

  9. DRF Django REST framework 之 路由器与版本控制组件(七)

    路由器 一些Web框架提供了用于自动确定应如何将应用程序的URL映射到处理传入请求的逻辑的功能. 而DRF的路由器组件也提供了一种简单,快速且一致的方式将视图逻辑映射到一组URL上. 路由器组件的使用 ...

  10. dockerfile-格式和使用

    一.dockerfile格式 Dockerfile是一个包含用于组合映像的命令文本文档,可以使用在命令行中调用任何命令,Docker通过读取dockerfile中的指令自动生成映像.docker bu ...