通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security提供了这样的功能,本文讲解一下在没有使用安全的时候如何手动实现这个功能
<强>演示技术选型强>
-
<李> SpringBoot李>
<李> JWT李>
<李>滤镜李>
<李>复述+ Redisson李>
智威汤逊(令牌)存储在复述中,类似JSessionId-Session的关系,用户登录后每次请求在标题中携带JWT
如果你是使用会话的话,也完全可以借鉴本文的思路,只是代码上需要加些改动
<强>
强>
<强>比较时间戳强>
维护一个用户名:jwtToken这样的一个键值在里德中,过滤器逻辑如下
公开课CompareKickOutFilter延伸KickOutFilter { @ autowired 私人UserService UserService; @Override 公共布尔isAccessAllowed (HttpServletRequest请求,HttpServletResponse响应){ 字符串标记=request.getHeader(“授权”); 字符串的用户名=JWTUtil.getUsername(令牌); 字符串userKey=前缀+同名的用户名; RBucket桶=redissonClient.getBucket (userKey)同名; 字符串redisToken=bucket.get (); 如果(token.equals (redisToken)) { 返回true; }else if (StringUtils.isBlank (redisToken)) { bucket.set(令牌); 其他}{ 长redisTokenUnixTime=JWTUtil。getClaim (redisToken createTime) .asLong (); 长tokenUnixTime=JWTUtil。getClaim(令牌,“createTime”) .asLong ();//令牌比;redisToken则覆盖 如果(tokenUnixTime.compareTo (redisTokenUnixTime)比;0){ bucket.set(令牌); 其他}{//注销当前令牌 userService.logout(令牌); sendJsonResponse(响应,4001,“您的账号已在其他设备登录”); 返回错误; } } 返回true; } } >之前 <强>队列踢出强>
公开课QueueKickOutFilter延伸KickOutFilter {/* * *踢出之前登录的/之后登录的用户默认踢出之前登录的用户 */私人布尔kickoutAfter=false;/* * *同一个帐号最大会话数默认1 */私人int maxSession=1; 公共空间setKickoutAfter(布尔kickoutAfter) { 这一点。kickoutAfter=kickoutAfter; } 公共空间setMaxSession (int maxSession) { 这一点。maxSession=maxSession; } @Override 公共布尔isAccessAllowed (HttpServletRequest请求,HttpServletResponse响应){抛出异常 字符串标记=request.getHeader(“授权”); UserBO currentSession=CurrentUser.get (); 断言。notNull (currentSession“currentSession不能空”); 字符串的用户名=currentSession.getUsername (); 字符串userKey=前缀+同名“deque_”+用户名; 字符串lockKey=PREFIX_LOCK +用户名; RLock锁=redissonClient.getLock (lockKey); 锁。锁(2,TimeUnit.SECONDS); 尝试{ RDeque双端队列=redissonClient.getDeque (userKey)同名;//如果队列里没有此令牌,且用户没有被踢出,放入队列 如果(! deque.contains(令牌),,currentSession.isKickout ()==false) { deque.push(令牌); }//如果队列里的sessionId数超出最大会话数,开始踢人 而(deque.size()在maxSession) { 字符串kickoutSessionId; 如果(kickoutAfter){//如果踢出后者 kickoutSessionId=deque.removeFirst (); 其他}{//否则踢出前者 kickoutSessionId=deque.removeLast (); } 尝试{ RBucket 桶=redissonClient.getBucket (kickoutSessionId); UserBO kickoutSession=bucket.get (); 如果(kickoutSession !=null) {//设置会话的解雇属性表示踢出了 kickoutSession.setKickout(真正的); bucket.set (kickoutSession); } }捕捉(异常e) { } }//如果被踢出了,直接退出,重定向到踢出后的地址 如果(currentSession.isKickout ()) {//会话被踢出了 尝试{//注销 userService.logout(令牌); sendJsonResponse(响应,4001,“您的账号已在其他设备登录”); }捕捉(异常e) { } 返回错误; } 最后}{ 如果(lock.isHeldByCurrentThread ()) { lock.unlock (); LOGGER.info (Thread.currentThread () . getname() +“解锁”); 其他}{ LOGGER.info (Thread.currentThread () . getname() +“已经自动释放锁”); } } 返回true; } } SpringBoot并发登录人数控制的实现方法