Salmon的全栈知识 Salmon的全栈知识
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Shiro

    • 权限概述
    • Shiro概述
    • 认证鉴权服务化
    • Web项目集成Shiro
    • shiro-client客户端
    • shiro-gateway网关
    • 实现分布式会话SessionManager
    • 限制密码重试次数
    • 在线并发登录人数控制
      • 1、实现原理
      • 2、代码实现
        • 【1】KickedOutAuthorizationFilter
        • 【2】修改ShiroConfig
        • 【3】修改authentication.properties
      • 3、测试
    • Springboot+Shiro+Jwt前后端分离鉴权
    • 分布式统一权限系统
  • Mybatis-Plus

    • 快速入门
    • 核心功能
    • 扩展功能
    • 插件功能
  • 《Java常用框架》笔记
  • Shiro
Salmon
2024-03-15
目录

在线并发登录人数控制

# 1、实现原理

在实际开发中,我们可能会遇到这样的需求,一个账号只允许同时一个在线,当账号在其他地方登陆的时候,会踢出前面登陆的账号,那我们怎么实现

  • 自定义过滤器:继承AccessControlFilter

  • 使用redis队列控制账号在线数目

    实现步骤:

1、只针对登录用户处理,首先判断是否登录
2、使用RedissionClien创建队列
3、判断当前sessionId是否存在于此用户的队列=key:登录名 value:多个sessionId
4、不存在则放入队列尾端==>存入sessionId
5、判断当前队列大小是否超过限定此账号的可在线人数
6、超过:
	*从队列头部拿到用户sessionId
	*从sessionManger根据sessionId拿到session
	*从sessionDao中移除session会话
7、未超过:放过操作

# 2、代码实现

# 【1】KickedOutAuthorizationFilter

package com.itheima.shiro.filter;

import com.itheima.shiro.core.impl.RedisSessionDao;
import com.itheima.shiro.utils.EmptyUtil;
import com.itheima.shiro.utils.ShiroUserUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.api.RDeque;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * @Description:
 */
@Log4j2
public class KickedOutAuthorizationFilter extends AccessControlFilter {

    private RedissonClient redissonClient;

    private SessionDAO redisSessionDao;

    private DefaultWebSessionManager sessionManager;

    public KickedOutAuthorizationFilter(RedissonClient redissonClient, SessionDAO redisSessionDao, DefaultWebSessionManager sessionManager) {
        this.redissonClient = redissonClient;
        this.redisSessionDao = redisSessionDao;
        this.sessionManager = sessionManager;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);
        if (!subject.isAuthenticated()) {
            //如果没有登录,直接进行之后的流程
            return true;
        }
        //存放session对象进入队列
        String sessionId = ShiroUserUtil.getShiroSessionId();
        String LoginName = ShiroUserUtil.getShiroUser().getLoginName();
        RDeque<String> queue = redissonClient.getDeque("KickedOutAuthorizationFilter:"+LoginName);
        //判断sessionId是否存在于此用户的队列中
        boolean flag = queue.contains(sessionId);
        if (!flag) {
            queue.addLast(sessionId);
        }
        //如果此时队列大于1,则开始踢人
        if (queue.size() > 1) {
            sessionId = queue.getFirst();
            queue.removeFirst();
            Session session = null;
            try {
                session = sessionManager.getSession(new DefaultSessionKey(sessionId));
            }catch (UnknownSessionException ex){
                log.info("session已经失效");
            }catch (ExpiredSessionException expiredSessionException){
                log.info("session已经过期");
            }
            if (!EmptyUtil.isNullOrEmpty(session)){
                redisSessionDao.delete(session);
            }
        }
        return true;
    }
}



# 【2】修改ShiroConfig

/**
  * @Description 自定义过滤器定义
  */
private Map<String, Filter> filters() {
    Map<String, Filter> map = new HashMap<String, Filter>();
    map.put("roleOr", new RolesOrAuthorizationFilter());
    map.put("kickedOut", new KickedOutAuthorizationFilter(redissonClient(), redisSessionDao(), shiroSessionManager()));
    return map;
}

# 【3】修改authentication.properties

#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=role-or[MangerRole,SuperAdmin]
#其他链接是需要登录的
/**=kickedOut,auth

# 3、测试

使用谷歌访问http://127.0.0.1/shiro/login,使用admin/pass登陆

1581948184529

使用IE再访问http://127.0.0.1/shiro/login,使用admin/pass登陆

1581948255302

再刷新谷歌浏览器,发现账号被踢出

1581948283827

上次更新: 2025/07/23, 01:37:33
限制密码重试次数
Springboot+Shiro+Jwt前后端分离鉴权

← 限制密码重试次数 Springboot+Shiro+Jwt前后端分离鉴权→

Theme by Vdoing | Copyright © 2022-2025 Salmon's Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式