在传统的项目中,同一账户是允许多人同时登录在线的,有的使用场景恰恰是不允许多人同时在线的,那么我们可以通过 Shiro 来控制并发登录,并实现后登录的用户,挤掉前面登录的用户

  • 1、并发登录过滤器
package com.asurplus.common.shiro;import com.asurplus.system.entity.SysUserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;/*** 同一用户登录后踢出前面的用户*/
public class KickoutSessionFilter extends AccessControlFilter {/*** 踢出后到的地址*/private String kickoutUrl;/*** 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户*/private boolean kickoutAfter = false;/*** 同一个帐号最大会话数 默认1*/private int maxSession = 1;/*** session管理器*/private SessionManager sessionManager;/*** 缓存管理器*/private Cache<String, Deque<Serializable>> cache;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}public void setKickoutAfter(boolean kickoutAfter) {this.kickoutAfter = kickoutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}public void setCacheManager(CacheManager cacheManager) {this.cache = cacheManager.getCache("kickoutSession");}/*** 是否允许访问*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request, response);if (!subject.isAuthenticated() && !subject.isRemembered()) {// 如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();SysUserInfo object = (SysUserInfo) SecurityUtils.getSubject().getPrincipal();Serializable sessionId = session.getId();// 同步控制Deque<Serializable> deque = cache.get(object.getAccount());if (deque == null) {deque = new LinkedList<Serializable>();cache.put(object.getAccount(), 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) {e.printStackTrace();}}// 如果被踢出了,直接退出,重定向到踢出后的地址if (session.getAttribute("kickout") != null) {// 会话被踢出了try {subject.logout();} catch (Exception e) {e.printStackTrace();}saveRequest(request);HttpServletRequest httpRequest = WebUtils.toHttp(request);// 如果是ajax请求if (isAjax(httpRequest)) {HttpServletResponse httpServletResponse = WebUtils.toHttp(response);// 使得http会话过期httpServletResponse.sendError(0);return false;} else {WebUtils.issueRedirect(request, response, kickoutUrl);return false;}}return true;}/*** 判断是否为ajax请求** @param request* @return boolean对象*/public static boolean isAjax(ServletRequest request) {return "XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"));}
}

这里我们使用了 ehcache,我们需要在 shiro-ehcache.xml 配置文件中,加一个存储对象,如下:

<!-- 并发登录控制 --><cache name="kickoutSession" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120"timeToLiveSeconds="120" maxElementsOnDisk="10000000" overflowToDisk="true" memoryStoreEvictionPolicy="LRU"/>
  • 2、注册过滤器
/**
* 并发登录控制
*
* @return
*/
@Bean
public KickoutSessionFilter kickoutSessionControlFilter() {KickoutSessionFilter kickoutSessionControlFilter = new KickoutSessionFilter();// 用于根据会话ID,获取会话进行踢出操作的;kickoutSessionControlFilter.setSessionManager(sessionManager());// 使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;kickoutSessionControlFilter.setCacheManager(ehCacheManager());// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;kickoutSessionControlFilter.setKickoutAfter(false);// 同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;kickoutSessionControlFilter.setMaxSession(1);// 被踢出后重定向到的地址;kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");return kickoutSessionControlFilter;
}

其中用到的 session 管理器,和 ehcache 管理器在之前的博客中都有讲到,本次不再赘述

我们将踢出的用户重定向到登录界面,并携带参数 kickout

  • 3、注入自定义过滤器
/*** 地址过滤器** @param securityManager* @return*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 设置securityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 设置登录urlshiroFilterFactoryBean.setLoginUrl("/login");// 设置主页urlshiroFilterFactoryBean.setSuccessUrl("/");// 设置未授权的urlshiroFilterFactoryBean.setUnauthorizedUrl("/403");// 自定义拦截器限制并发人数LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();// 限制同一帐号同时在线的个数filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 注销登录filterChainDefinitionMap.put("/loginOut", "logout");// 开放登录接口filterChainDefinitionMap.put("/doLogin", "anon");// 开放获取登录验证码接口filterChainDefinitionMap.put("/kaptcha/**", "anon");// 开放Api接口filterChainDefinitionMap.put("/api/**", "anon");// 开放微信接口filterChainDefinitionMap.put("/weixin/**", "anon");// 开放websocket接口filterChainDefinitionMap.put("/websocket/**", "anon");// 开放接口文档filterChainDefinitionMap.put("/doc.html", "anon");filterChainDefinitionMap.put("/service-worker.js", "anon");filterChainDefinitionMap.put("/swagger-resources/**", "anon");filterChainDefinitionMap.put("/webjars/**", "anon");filterChainDefinitionMap.put("/v2/**", "anon");// 开放静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/img/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/layui/**", "anon");filterChainDefinitionMap.put("/layuimini/**", "anon");filterChainDefinitionMap.put("/module/**", "anon");filterChainDefinitionMap.put("/upload/**", "anon");// 其余url全部拦截,必须放在最后filterChainDefinitionMap.put("/**", "kickout,user");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;
}

我们在 ShiroFilterFactoryBean 对象中注入了自定义过滤器,并在最后的地址拦截规则中增加了 kickout,即使我们开启了记住登录功能,该用户也会被踢下线

  • 4、提示信息

我们在登录页面中,需要获取地址中是否有 kickout 参数

// 是否被挤下线
if(location.href.indexOf("kickout") > 0){setTimeout(function () {layNotify.notice({title: "登录提示",type: "error",message: '您的账户已在另一台设备上登录,如非本人操作,请立即修改密码!'});}, 1000)
}

这样后面登录的用户就会挤掉前面登录用户,导致前面登录的用户被踢下线了

如您在阅读中发现不足,欢迎留言!!!

【Shiro】7、Shiro实现控制用户并发登录并踢人下线相关推荐

  1. Shiro实现session限制登录数量踢人下线

    Shiro实现session限制登录数量踢人下线 前言 实现 ■ 架构准备 ShiroConfig ■ redis内的存储分布 ■ 代码修改 修改 JedisSessionDAO 修改 SystemA ...

  2. java实现踢下线用户_java中如何踢人下线?封禁某个账号后使其会话立即掉线!...

    需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...

  3. java实现踢下线用户_浅谈踢人下线的设计思路!(附代码实现方案)

    前言 前两天写了一篇文章,主要讲了下java中如何实现踢人下线,原文连接:java中如何踢人下线?封禁某个帐号后使其会话当即掉线!前端 原本只是简单阐述一下踢人下线的业务场景和实现方案,没想到引出那么 ...

  4. 新增公告提示功能,支持用户异地登录提醒和授权过期通知,JumpServer堡垒机v2.15.0发布

    11月1日,JumpServer开源堡垒机正式发布v2.15.0版本.在这一版本中,JumpServer新增公告提示功能,管理员可以根据不同的场景向所有用户实时发布重要通知:用户登录方面,新版本支持用 ...

  5. springboot + shiro 尝试登录次数限制与并发登录人数控制

    源码项目地址 尝试登录次数控制实现 实现原理 Realm在验证用户身份的时候,要进行密码匹配.最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatche ...

  6. Shiro并发登录人数控制遇到的问题和解决

    shiro并发登录人数控制遇到的问题和解决 问题1:KickoutSessionControlFilter不起作用 问题2:KickoutSessionControlFilter中cache为null ...

  7. 使用shiro+aop实现权限控制

    对于一个后台管理系统,控制访问权限的功能必不可少,本章详细介绍如何通过shiro+aop来实现权限控制 一:建库建表 实现权限管理一般需要5张表:用户表.角色表.权限表.用户-角色关系表.角色-权限关 ...

  8. 软件评测-信息安全-应用安全-资源控制-用户登录限制(中)

    最近需要把通用权限管理系统送到软件评测中心进行信息安全测试,其中有就有一项检查内容叫:"资源控制-用户登录限制",为了达到这个检查项目的要求,我们程序也进行了改进,同时也是为了达到 ...

  9. 模仿爱奇艺账号登录限制人数,SpringBoot 并发登录人数控制,踢人功能

    通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 技术选 ...

最新文章

  1. cuda合并访问的要求_在 CUDA C / C ++ 中使用共享内存
  2. java静态分派_Java基础——重载、静态分派与动态分派
  3. mysql筛选字符个数为8的_听说Mysql你很豪横?-------------分分钟带你玩转SQL高级查询语句(常用查询,正则表达式,运算符)...
  4. MyEclipse 代码自动提示功能失效 提示No Default Proposals 或 no completions available 的解决方法 转...
  5. salt-API基本验证命令
  6. centos6 和 centos7 防火墙基本操作
  7. 游戏策划学习(一)游戏研发基础
  8. 【系统分析师之路】系统分析师冲刺习题集(数学与经济管理)
  9. ubuntu 开机后 按键 鼠标不能用
  10. 计算机电子表格的优点,信息技术《电子表格的特点及应用》的说课稿
  11. 科研教育「双目视觉技术」首选!维视MV-VS220双目立体视觉系统开发平台
  12. 【Java 类和对象】
  13. SAP资产折旧-工作量法业务实践和实操
  14. 论文笔记:Editing-Based SQL Query Generation for Cross-Domain Context-Dependent Questions
  15. Luogu P3385 【模板】负环 - 题解
  16. 远程缝制葡萄皮,成本仅1万元,华为200万年薪博士杰作
  17. 电工与电子技术实验——叠加定理与戴维南定理
  18. 什么PDF在线压缩器好用,怎么操作?
  19. 【智慧农业】智能灌溉系统应用方案
  20. 跑深度模型的显卡_2020年人工智能深度学习 GPU 解决方案推荐

热门文章

  1. Angular 实现树形菜单(多级菜单)功能模块
  2. 迄今微软不同时期发布的SQL Server各版本之间的大致区别,供参考查阅
  3. CSS_浮动排版布局
  4. 谁能给个oracle邮箱,谁能给我一个电子邮箱
  5. 信息学奥赛 CSP-J2 CSP-S2第2轮 复赛 如何取得好成绩
  6. PHP有三宝,三、认识三宝
  7. 办理护照(学生集体户口)~备用
  8. 移动硬盘无法读取与快速修复方法
  9. 笨重的多用开源生物识别腕带 LEWE
  10. 八骏登场 学子圆梦 一卷在手 良师益友