java 登录并发_并发登录控制 - 跟我学 Apache Shiro_教程_Java开发社区

该博客介绍了如何在Spring和Shiro框架中实现并发登录控制。通过ShiroFilter扩展KickoutSessionControlFilter,配置限制同一用户最大登录人数,超过限制则踢出已登录用户。示例代码展示了配置和核心逻辑,包括从缓存获取用户会话、判断是否踢出用户以及处理踢出操作。
摘要由CSDN通过智能技术生成

并发登录人数控制

在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录;要么踢出前者登录(强制退出)。比如 spring security 就直接提供了相应的功能;Shiro 的话没有提供默认实现,不过可以很容易的在 Shiro 中加入这个功能。

示例代码基于《第十六章 综合实例》完成,通过 Shiro Filter 机制扩展 KickoutSessionControlFilter 完成。

首先来看看如何配置使用(spring-config-shiro.xml)

kickoutSessionControlFilter 用于控制并发登录人数的

class="com.github.zhangkaitao.shiro.chapter18.web.shiro.filter.KickoutSessionControlFilter">

 

cacheManager:使用 cacheManager 获取相应的 cache 来缓存用户登录的会话;用于保存用户—会话之间的关系的;

sessionManager:用于根据会话 ID,获取会话进行踢出操作的;

kickoutAfter:是否踢出后来登录的,默认是 false;即后者登录的用户踢出前者登录的用户;

maxSession:同一个用户最大的会话数,默认 1;比如 2 的意思是同一个用户允许最多同时两个人登录;

kickoutUrl:被踢出后重定向到的地址;

shiroFilter 配置

/login = authc

/logout = logout

/authenticated = authc

/** = kickout,user,sysUser

此处配置除了登录等之外的地址都走 kickout 拦截器进行并发登录控制。

测试

此处因为 maxSession=2,所以需要打开 3 个浏览器(需要不同的浏览器,如 IE、Chrome、Firefox),分别访问 http://localhost:8080/chapter18/ 进行登录;然后刷新第一次打开的浏览器,将会被强制退出,如显示下图:

431c9f7319ae0a0860e6dfbc055319a8.png

KickoutSessionControlFilter 核心代码:

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

Subject subject = getSubject(request, response);

if(!subject.isAuthenticated() && !subject.isRemembered()) {

//如果没有登录,直接进行之后的流程

return true;

}

Session session = subject.getSession();

String username = (String) subject.getPrincipal();

Serializable sessionId = session.getId();

//TODO 同步控制

Deque deque = cache.get(username);

if(deque == null) {

deque = new LinkedList();

cache.put(username, deque);

}

//如果队列里没有此sessionId,且用户没有被踢出;放入队列

if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {

deque.push(sessionId);

}

//如果队列里的sessionId数超出最大会话数,开始踢人

while(deque.size() > maxSession) {

Serializable kickoutSessionId = null;

if(kickoutAfter) { //如果踢出后者

kickoutSessionId = deque.removeFirst();

} else { //否则踢出前者

kickoutSessionId = deque.removeLast();

}

try {

Session kickoutSession =

sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));

if(kickoutSession != null) {

//设置会话的kickout属性表示踢出了

kickoutSession.setAttribute("kickout", true);

}

} catch (Exception e) {//ignore exception

}

}

//如果被踢出了,直接退出,重定向到踢出后的地址

if (session.getAttribute("kickout") != null) {

//会话被踢出了

try {

subject.logout();

} catch (Exception e) { //ignore

}

saveRequest(request);

WebUtils.issueRedirect(request, response, kickoutUrl);

return false;

}

return true;

此处使用了 Cache 缓存用户名—会话 id 之间的关系;如果量比较大可以考虑如持久化到数据库 / 其他带持久化的 Cache 中;另外此处没有并发控制的同步实现,可以考虑根据用户名获取锁来控制,减少锁的粒度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>