Shiro 控制并发登录人数限制及登录踢出的实现代码
作者:AinUser 发布时间:2023-09-18 02:03:56
标签:shiro,并发,登录,踢出
我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。
这样保证了一个帐号只能同时一个人使用。那么下面来讲解一下 Shiro 怎么实现这个功能,现在是用到了缓存 Redis 。我们也可以用其他缓存。如果是单个点,直接用一个静态的Map<String,Object> 或者 Ehcache 即可。
XML配置。
<!-- session 校验单个用户是否多次登录 -->
<bean id="kickoutSessionFilter" class="com.sojson.core.shiro.filter.KickoutSessionFilter">
<property name="kickoutUrl" value="/u/login.shtml?kickout"/>
</bean>
<!-- 静态注入 jedisShiroSessionRepository-->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="com.sojson.core.shiro.filter.KickoutSessionFilter.setShiroSessionRepository"/>
<property name="arguments" ref="jedisShiroSessionRepository"/>
</bean>
这里用到了静态注入。如果不了解请看这篇:Spring 静态注入讲解(MethodInvokingFactoryBean)
加入到 shiro 的Filter 拦截序列
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/u/login.shtml" />
<!-- TODO 待提取 -->
<property name="successUrl" value="/" />
<property name="unauthorizedUrl" value="/?login" />
<property name="filterChainDefinitions" value="#{shiroManager.loadFilterChainDefinitions()}"/>
<property name="filters">
<util:map>
<entry key="login" value-ref="login"></entry>
<entry key="role" value-ref="role"></entry>
<entry key="simple" value-ref="simple"></entry>
<entry key="permission" value-ref="permission"></entry>
<entry key="kickout" value-ref="kickoutSessionFilter"></entry>
</util:map>
</property>
</bean>
Java代码,下面看实现的Filter代码。
package com.sojson.core.shiro.filter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONObject;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import com.sojson.common.utils.LoggerUtils;
import com.sojson.core.shiro.cache.VCache;
import com.sojson.core.shiro.session.ShiroSessionRepository;
import com.sojson.core.shiro.token.manager.TokenManager;
/**
*
* 开发公司:SOJSON在线工具 <p>
* 版权所有:© www.sojson.com<p>
* 博客地址:http://www.sojson.com/blog/ <p>
* <p>
*
* 相同帐号登录控制
*
* <p>
*
* 区分责任人日期说明<br/>
* 创建周柏成2016年6月2日 <br/>
*
* @author zhou-baicheng
* @email so@sojson.com
* @version 1.0,2016年6月2日 <br/>
*
*/
@SuppressWarnings({"unchecked","static-access"})
public class KickoutSessionFilter extends AccessControlFilter {
//静态注入
static String kickoutUrl;
//在线用户
final static String ONLINE_USER = KickoutSessionFilter.class.getCanonicalName()+ "_online_user";
//踢出状态,true标示踢出
final static String KICKOUT_STATUS = KickoutSessionFilter.class.getCanonicalName()+ "_kickout_status";
static VCache cache;
//session获取
static ShiroSessionRepository shiroSessionRepository;
@Override
protected boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) throws Exception {
HttpServletRequest httpRequest = ((HttpServletRequest)request);
String url = httpRequest.getRequestURI();
Subject subject = getSubject(request, response);
//如果是相关目录 or 如果没有登录 就直接return true
if(url.startsWith("/open/") || (!subject.isAuthenticated() && !subject.isRemembered())){
return Boolean.TRUE;
}
Session session = subject.getSession();
Serializable sessionId = session.getId();
/**
* 判断是否已经踢出
* 1.如果是Ajax 访问,那么给予json返回值提示。
* 2.如果是普通请求,直接跳转到登录页
*/
Boolean marker = (Boolean)session.getAttribute(KICKOUT_STATUS);
if (null != marker && marker ) {
Map<String, String> resultMap = new HashMap<String, String>();
//判断是不是Ajax请求
if (ShiroFilterUtils.isAjax(request) ) {
LoggerUtils.debug(getClass(), "当前用户已经在其他地方登录,并且是Ajax请求!");
resultMap.put("user_status", "300");
resultMap.put("message", "您已经在其他地方登录,请重新登录!");
out(response, resultMap);
}
return Boolean.FALSE;
}
//从缓存获取用户-Session信息 <UserId,SessionId>
LinkedHashMap<Long, Serializable> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class);
//如果不存在,创建一个新的
infoMap = null == infoMap ? new LinkedHashMap<Long, Serializable>() : infoMap;
//获取tokenId
Long userId = TokenManager.getUserId();
//如果已经包含当前Session,并且是同一个用户,跳过。
if(infoMap.containsKey(userId) && infoMap.containsValue(sessionId)){
//更新存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
cache.setex(ONLINE_USER, infoMap, 3600);
return Boolean.TRUE;
}
//如果用户相同,Session不相同,那么就要处理了
/**
* 如果用户Id相同,Session不相同
* 1.获取到原来的session,并且标记为踢出。
* 2.继续走
*/
if(infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){
Serializable oldSessionId = infoMap.get(userId);
Session oldSession = shiroSessionRepository.getSession(oldSessionId);
if(null != oldSession){
//标记session已经踢出
oldSession.setAttribute(KICKOUT_STATUS, Boolean.TRUE);
shiroSessionRepository.saveSession(oldSession);//更新session
LoggerUtils.fmtDebug(getClass(), "kickout old session success,oldId[%s]",oldSessionId);
}else{
shiroSessionRepository.deleteSession(oldSessionId);
infoMap.remove(userId);
//存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
cache.setex(ONLINE_USER, infoMap, 3600);
}
return Boolean.TRUE;
}
if(!infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){
infoMap.put(userId, sessionId);
//存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
cache.setex(ONLINE_USER, infoMap, 3600);
}
return Boolean.TRUE;
}
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//先退出
Subject subject = getSubject(request, response);
subject.logout();
WebUtils.getSavedRequest(request);
//再重定向
WebUtils.issueRedirect(request, response,kickoutUrl);
return false;
}
private void out(ServletResponse hresponse, Map<String, String> resultMap)
throws IOException {
try {
hresponse.setCharacterEncoding("UTF-8");
PrintWriter out = hresponse.getWriter();
out.println(JSONObject.fromObject(resultMap).toString());
out.flush();
out.close();
} catch (Exception e) {
LoggerUtils.error(getClass(), "KickoutSessionFilter.class 输出JSON异常,可以忽略。");
}
}
public static void setShiroSessionRepository(
ShiroSessionRepository shiroSessionRepository) {
KickoutSessionFilter.shiroSessionRepository = shiroSessionRepository;
}
public static String getKickoutUrl() {
return kickoutUrl;
}
public static void setKickoutUrl(String kickoutUrl) {
KickoutSessionFilter.kickoutUrl = kickoutUrl;
}
}
前端页面(登录页面)代码。
try{
var _href = window.location.href+"";
if(_href && _href.indexOf('?kickout')!=-1){
layer.msg('您已经被踢出,请重新登录!');
}
}catch(e){
}
Ok了,这样效果就出来了。(效果图)
总结
以上所述是小编给大家介绍的Shiro 控制并发登录人数限制及登录踢出的实现代码网站的支持!
来源:http://blog.csdn.net/ainuser/article/details/62048551


猜你喜欢
- synchronized关键字,一般称之为”同步锁“,用它来修饰需要同步的方法和需要同步代码块,默认是当前对象作为锁的对象。同步锁锁的是同一
- 本文实例讲述了C#检测远程计算机端口是否打开的方法。分享给大家供大家参考。具体分析如下:这段C#代码用于检测远程计算机的3389端口是否处理
- 简介本文用示例介绍SpringBoot如何解决雪花算法主键ID传到前端后精度丢失问题。问题描述Java后端Long类型的范围-2^63~2^
- 如下所示:using System;using System.Collections.Generic;using System.Diagno
- 本文为大家分享了使用静态关键字实现单例模式的具体代码,供大家参考,具体内容如下单例模式:只能获得某个类的唯一一个实例单例模式,不管什么时间点
- 问题窥探在有些时候,我们拿到了一张图片。这张图片的格式是 JPG 还是 PNG?是 BMP 还是只有一帧的 GIF?虽然在大部分情况下,你确
- Spring Data JPA 映射VO/DTO对象在项目开发中,时常需要根据业务需求来映射VO/DTO对象(这两个概念理解感觉很模糊- 。
- 本文实例分析了Android开发之TimePicker控件用法。分享给大家供大家参考,具体如下:新建项目:New Android Proje
- 最近对接接口的时候,需要根据对方的请求数据类型来进行传值,常用的就是application/x-www-form-urlencoded,aj
- 有时我们需要判断某个类是否实现了某个接口(Interface),比如在使用反射机制(Reflection)来查找特定类型的时候。简单来说,可
- springboot初始化器新建项目项目结构idea工具类中初始化本地git仓库选择当前项目目录即可工具类由VCS变成了Gitadd 到缓存
- 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况
- 1.多节点无缝切换问题分布式节点中的服务宕机或者重启不影响客户端使用分布式节点中的服务宕机重启不影响业务服务内部通信如果在某个分布式系统中想
- 这篇文章主要介绍了Spring整合MyBatis图示过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需
- 1.前置准备默认服务器上的hadoop服务已经启动本地如果是windows环境,需要本地配置下hadoop的环境变量本地配置hadoop的环
- java 实现文件夹的拷贝实例代码 这里就直接上代码,
- BitArray的基础可以看菜鸟编程BitArray 类管理一个紧凑型的位值数组,它使用布尔值来表示,其中 true 表示位是开启的(1),
- Mybatis入门-基于配置实现单表的增删改查Mybatis简介官网链接:https://mybatis.org/mybatis-3/zh/
- 前言 OFD是国家标准版式文档格式,于2016年生效。OFD文档国家标准参见《电子文件存储与交换格式版式文档》。既然是国家标准,O
- 效果图如下: Recyclerview 实现多选,单选,全选,反选,批量删除的步骤1.在Recyclerview布局中