在线并发登录人数控制
# 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登陆

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

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

上次更新: 2025/07/23, 01:37:33