SpringBoot system control the number of concurrent login

The system will limit the number of registrants is usually the same account, log in or limit people who log on or kicked out of the former, Spring Security provides such functions, we explain in the absence of the use of Security of how to manually implement this function

demo technology selection

  • SpringBoot
  • JWT
  • Filter
  • Redis + Redisson

JWT (token) is stored in Redis, a similar relationship JSessionId-Session after the user logs each request to carry jwt in the Header
If you are using a session, it also can learn from this article thinking, just needs some modification on the code

Both implementations ideas

Compare timestamp

Maintaining a username: such a jwtToken key-value in the Reids, Filter logic is as follows

 

Open big picture point may be unclear

 
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);
            } The else {
                 // off the current token 
                userService.logout (token);
                sendJsonResponse (the Response, 4001, "Your account has been registered in other equipment" );
                 return  false ;
            }
        }
        return true;

    }
}

Kicked out of the queue

 
 
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

作者:殷天文

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

推荐阅读:

SpringBoot基于数据库的定时任务实现

Java中大量if...else语句的消除替代方案

Java8中遍历Map的常用四种方式

扫码关注公众号,发送关键词获取相关资料:
  1. 发“Springboot”领取电商项目实战源码;

  2. 发“SpringCloud”领取学习实战资料;

 

Guess you like

Origin www.cnblogs.com/lyn20141231/p/12110917.html