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

文章目录

          • 一、尝试登录次数控制实现
            • 1. 实现原理
            • 2. maven依赖
            • 3. ehcache配置
            • 4. RetryLimitCredentialsMatcher
            • 5. Shiro配置修改
            • 6. realm添加认证器
            • 7. 并发在线人数控制实现
            • 8. ehcache配置
            • 9. shiro配置
            • 10. shiro过滤链中加入并发登录人数过滤器
            • 11. 源码地址
            • 12. 参考博客

一、尝试登录次数控制实现
1. 实现原理

Realm在验证用户身份的时候,要进行密码匹配。最简单的情况就是明文直接匹配,然后就是加密匹配,这里的匹配工作则就是交给CredentialsMatcher来完成的。我们在这里继承这个接口,自定义一个密码匹配器,缓存入键值对用户名以及匹配次数,若通过密码匹配,则删除该键值对,若不匹配则匹配次数自增。超过给定的次数限制则抛出错误。这里缓存用的是ehcache。

2. maven依赖

shiro-ehcache配置

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- mybatis-plus依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency><!-- 引入thymelaf 则不需要引入web依赖,若不需要thymelaf则需要添加spring-boot-starter-web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.7.0</version></dependency><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>
3. ehcache配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es"><diskStore path="java.io.tmpdir"/><!--name:缓存名称。maxElementsInMemory:缓存最大数目maxElementsOnDisk:硬盘最大缓存个数。eternal:对象是否永久有效,一但设置了,timeout将不起作用。overflowToDisk:是否保存到磁盘,当系统当机时timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。clearOnFlush:内存数量最大时是否清除。memoryStoreEvictionPolicy:Ehcache的三种清空策略;FIFO,first in first out,这个是大家最熟的,先进先出。LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。--><defaultCachemaxElementsInMemory="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="false"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"/><!-- 登录记录缓存锁定10分钟 --><cache name="passwordRetryCache"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache><!-- 用户队列缓存10分钟 --><cache name="shiro-kickout-session"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache></ehcache>
4. RetryLimitCredentialsMatcher
package com.gblfy.shiro.config.shiro;import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.util.concurrent.atomic.AtomicInteger;/** * 验证器,增加了登录次数校验功能 * 此类不对密码加密* @author gblfy* @date 2020-12-16*/
@Component
public class RetryLimitCredentialsMatcher extends SimpleCredentialsMatcher {private static final Logger log = LoggerFactory.getLogger(RetryLimitCredentialsMatcher.class);private int maxRetryNum = 5;private EhCacheManager shiroEhcacheManager;public void setMaxRetryNum(int maxRetryNum) {this.maxRetryNum = maxRetryNum;}public RetryLimitCredentialsMatcher(EhCacheManager shiroEhcacheManager) {this.shiroEhcacheManager = shiroEhcacheManager; }@Override  public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {Cache<String, AtomicInteger> passwordRetryCache = shiroEhcacheManager.getCache("passwordRetryCache");String username = (String) token.getPrincipal();  //retry count + 1  AtomicInteger retryCount = passwordRetryCache.get(username);  if (null == retryCount) {  retryCount = new AtomicInteger(0);passwordRetryCache.put(username, retryCount);  }if (retryCount.incrementAndGet() > maxRetryNum) {log.warn("用户[{}]进行登录验证..失败验证超过{}次", username, maxRetryNum);throw new ExcessiveAttemptsException("username: " + username + " tried to login more than 5 times in period");}  boolean matches = super.doCredentialsMatch(token, info);  if (matches) {  //clear retry data  passwordRetryCache.remove(username);  }  return matches;  }  
} 
5. Shiro配置修改

注入CredentialsMatcher

     /*** 缓存管理器* @return cacheManager*/@Beanpublic EhCacheManager ehCacheManager(){EhCacheManager cacheManager = new EhCacheManager();cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");return cacheManager;}/*** 限制登录次数* @return 匹配器*/@Beanpublic CredentialsMatcher retryLimitCredentialsMatcher() {RetryLimitCredentialsMatcher retryLimitCredentialsMatcher = new RetryLimitCredentialsMatcher(ehCacheManager());retryLimitCredentialsMatcher.setMaxRetryNum(5);return retryLimitCredentialsMatcher;}
6. realm添加认证器
myShiroRealm.setCredentialsMatcher(retryLimitCredentialsMatcher());
7. 并发在线人数控制实现

KickoutSessionControlFilter

package com.gblfy.shiro.config.shiro.filter;import com.gblfy.shiro.entity.UserInfo;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Deque;
import java.util.LinkedList;/*** 并发登录人数控制** @author gblfy* @date 2020-12-16*/
public class KickoutSessionControlFilter extends AccessControlFilter {private static final Logger logger = LoggerFactory.getLogger(KickoutSessionControlFilter.class);/*** 踢出后到的地址*/private String kickoutUrl;/*** 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户*/private boolean kickoutAfter = false;/*** 同一个帐号最大会话数 默认1*/private int maxSession = 1;private String kickoutAttrName = "kickout";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;}/*** 设置Cache的key的前缀*/public void setCacheManager(CacheManager cacheManager) {this.cache = cacheManager.getCache("shiro-kickout-session");}@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();UserInfo user = (UserInfo) subject.getPrincipal();String username = user.getUsername();Serializable sessionId = session.getId();logger.info("进入KickoutControl, sessionId:{}", sessionId);//读取缓存 没有就存入Deque<Serializable> deque = cache.get(username);if (deque == null) {deque = new LinkedList<Serializable>();cache.put(username, deque);}//如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId) && session.getAttribute(kickoutAttrName) == null) {//将sessionId存入队列deque.push(sessionId);}logger.info("deque.size:{}", deque.size());//如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {Serializable kickoutSessionId = null;if (kickoutAfter) {//如果踢出后者kickoutSessionId = deque.removeFirst();} else {//否则踢出前者kickoutSessionId = deque.removeLast();}//踢出后再更新下缓存队列cache.put(username, deque);try {//获取被踢出的sessionId的session对象Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (kickoutSession != null) {//设置会话的kickout属性表示踢出了kickoutSession.setAttribute(kickoutAttrName, true);}} catch (Exception e) {logger.error(e.getMessage());}}//如果被踢出了,直接退出,重定向到踢出后的地址if (session.getAttribute(kickoutAttrName) != null && (Boolean) session.getAttribute(kickoutAttrName) == true) {//会话被踢出了try {//退出登录subject.logout();} catch (Exception e) {logger.warn(e.getMessage());e.printStackTrace();}saveRequest(request);//重定向logger.info("用户登录人数超过限制, 重定向到{}", kickoutUrl);String reason = URLEncoder.encode("账户已超过登录人数限制", "UTF-8");String redirectUrl = kickoutUrl + (kickoutUrl.contains("?") ? "&" : "?") + "shiroLoginFailure=" + reason;WebUtils.issueRedirect(request, response, redirectUrl);return false;}return true;}
}
8. ehcache配置

ehcache-shiro.xml加入

<!-- 用户并发登陆 --><cache name="shiro-kickout-session"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"></cache>
9. shiro配置

ShiroConfig.java中注入相关对象

/*** 会话管理器* @return sessionManager*/@Beanpublic DefaultWebSessionManager configWebSessionManager(){DefaultWebSessionManager manager = new DefaultWebSessionManager();// 加入缓存管理器manager.setCacheManager(ehCacheManager());// 删除过期的sessionmanager.setDeleteInvalidSessions(true);// 设置全局session超时时间manager.setGlobalSessionTimeout(1 * 60 *1000);// 是否定时检查sessionmanager.setSessionValidationSchedulerEnabled(true);manager.setSessionValidationScheduler(configSessionValidationScheduler());manager.setSessionIdUrlRewritingEnabled(false);manager.setSessionIdCookieEnabled(true);return manager;}/*** session会话验证调度器* @return session会话验证调度器*/@Beanpublic ExecutorServiceSessionValidationScheduler configSessionValidationScheduler() {ExecutorServiceSessionValidationScheduler sessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();//设置session的失效扫描间隔,单位为毫秒sessionValidationScheduler.setInterval(300*1000);return sessionValidationScheduler;}
10. shiro过滤链中加入并发登录人数过滤器
filterChainDefinitionMap.put("/**", "kickout,user");

访问任意链接均需要认证通过以及限制并发登录次数

11. 源码地址

https://gitee.com/gb_90/shiro

12. 参考博客

springboot整合shiro-在线人数以及并发登录人数控制(七)
https://blog.csdn.net/qq_34021712/article/details/80457041

springboot + shiro 尝试登录次数限制与并发登录人数控制
https://blog.csdn.net/weixin_33775572/article/details/91459773

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519098.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

np.where多个筛选条件

a [0,1,2,3,4,5] 想要筛选出“大于等于2并且小于等于4”的数字下标&#xff0c;首先尝试了如下写法 import numpy as np a np.arange(6) print(np.where(a>2 & a<4))程序会报错 Traceback (most recent call last):File "C:\Program Files\JetBrains\PyCha…

c语言printf里的自增,笔试题记录:C语言——函数printf()的执行机制;先自增与后自增的区别;取值运算与自增运算的优先级...

考点&#xff1a;函数printf()的执行机制&#xff1a;参数按照从右至左的顺序入栈&#xff0c;在调用时出栈&#xff1b;考察运算符“”在变量之前、变量之后的运算机制&#xff0c;即是先赋值/还是先自增*(p)、*(p)、*(p1)之间的区别后面两项主要是考察&#xff1a;取值运算与…

分布式系统:CAP 理论的前世今生

CAP 理论是分布式系统设计中的一个重要理论&#xff0c;虽然它为系统设计提供了非常有用的依据&#xff0c;但是也带来了很多误解。本文将从 CAP 诞生的背景说起&#xff0c;然后对理论进行解释&#xff0c;最后对 CAP 在当前背景下的一些新理解进行分析&#xff0c;澄清一些对…

你的企业混合云了吗?来看看评估混合云解决方案时要注意的6个原则!

作者| Matt Kimball翻译 | 天道酬勤&#xff0c;编辑 | Carol出品| CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;混合云如今很流行。几乎每个IT公司都声称已部署了解决方案&#xff0c;但实际上实现的却很少。相反&#xff0c;它们拥有与多个云实例进行某种程度…

Sentinel 成为 Spring Cloud 官方推荐的主流熔断降级方案

近日&#xff0c;Sentinel 贡献的 spring-cloud-circuitbreaker-sentinel 模块正式被Spring Cloud社区合并至 Spring Cloud Circuit Breaker&#xff0c;由此&#xff0c;Sentinel 加入了 Spring Cloud Circuit Breaker 俱乐部&#xff0c;成为 Spring Cloud 官方的主流推荐选…

c语言int超出范围字符串,Go返回int64类型字段超出javascript Number范围的解决方法...

Go返回int64类型字段超出javascript Number范围的解决方法最近在项目中&#xff0c;一个go服务给前端提供了一个接口&#xff0c;返回json格式数据&#xff0c;其中Int64字段会超出javascript Number可表示的最大的Int值会丢精度&#xff0c;可以通过返回string类型值来屏蔽这个…

构建可靠系统的原则与实践

随着阿里技术的发展&#xff0c;我们的技术系统越来越成为社会的基础设施&#xff0c;对于这些系统的可靠性要求也就越来越高。但是实际上很多的基础的产品和系统确仍然会出现一些稳定性问题&#xff0c;那么如何才能构建可靠的系统呢&#xff1f;是不是制定非常严格而细致的规…

看完就能独自把集群搭起来!Hadoop HDFS完全分布式环境搭建以及技术详解

作者 | 慢慢变成大佬责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;在文章开始之前&#xff0c;作者想要告诉大家&#xff1a;读懂本篇文章&#xff0c;能让小白快速入门&#xff0c;并且能够搭建完全分布式的集群&#xff0c;以及能对Hadoop …

使用idea创建JavaWeb项目

【第一步】 File—New—Project 【第二步】 选择Java Enterprise版本&#xff0c;然后配置tomcat 注意&#xff1a;这里关联的tomcat home指的是tomcat的解压目录&#xff08;bin目录的上一级目录&#xff09;&#xff1b; 【第三步】 选择使用模板创建项目 【第四步】 填写…

阿里云栖开发者沙龙PHP技术专场-聊聊服务稳定性保障这些事

本文主要带大家了解服务稳定性的重要性和相关策略。策略大概分两部分&#xff0c;第一方面从架构层面介绍保障服务稳定性的常见策略&#xff08;限流&#xff0c;降级&#xff0c;隔离&#xff0c;超时&#xff0c;重试和集群&#xff09;。第二个方面是从流程方面&#xff08;…

ccs读取dat文件c语言程序,TMS320DM642学习----第六篇(CCS中.dat文件类型详解)

1、如下为.dat文件中文件头的基本格式&#xff1a;MagicNumber Format StartingAddress PageNum Length [NewFormat]下面是分别的解释&#xff1a;MagicNumber&#xff1a;1651.Format&#xff1a;a number from 1 to 4, indicating the format of the samples in the file. Th…

阿里开发者招聘节 | 面试题06-07: MySQL的数据如何恢复到任意时间点

为帮助开发者们提升面试技能、有机会入职阿里&#xff0c;云栖社区特别制作了这个专辑——阿里巴巴资深技术专家们结合多年的工作、面试经验总结提炼而成的面试真题这一次将陆续放出&#xff08;面试题官方参考答案将在专辑结束后统一汇总分享&#xff0c;点此进入答题并围观他…

通过接口操作MyBatis及数据库配置文件

优点&#xff1a; 不用每次实例化SqlSession配置优于硬编码减少sql书写错误的概率规范代码&#xff0c;面向接口服务 文章目录一、回顾二、如何通过接口操作MyBatis2.1. 文件结构2.2. 依赖jar包2.3. 表结构2.4. mybatis配置文件2.5. db配置文件2.6. 获取SqlSession工具类2.7. 基…

厉害!中国AI企业50强榜单!看完员工待遇,网友:我酸了!

有自媒体说&#xff0c;现在各大国对AI的部署&#xff0c;就像对“核武器”的部署一样&#xff0c;这份脑力和智力的比拼&#xff0c;超级大国都绝对不能输。在最近&#xff0c;赛迪研究院就发布了「2019人工智能企业综合实力100强名单」&#xff0c;BAT毫无疑问霸榜&#xff0…

蚂蚁金服SOFA开源负责人鲁直:不只是中间件,未来会开源更多

近日&#xff0c;技术媒体Linux中国的创始人王兴宇对蚂蚁金服SOFA开源负责人鲁直&#xff0c;就SOFA 5、ServiceMesh、Serverless、Seata等技术内容进行了探讨&#xff0c;以下为专访文章。 虽然我和鲁直在微信上已经联系很久了&#xff0c;但这还是第一次见面。交谈中&#x…

c语言 已知某系统在通信联络中,数据结构(习题)..doc

数据结构(习题).题1.1数据结构在计算机内存中的表示是指———。A&#xff0e;数据的存储结构 B&#xff0e;数据元素C&#xff0e;数据的逻辑结构 D&#xff0e;数据元素之间的关系题1.2从逻辑上可把数据结构分为——。A.动态结构和静态结构 B.顺序结构和链式结构C.线性结构和…

log4j2.xml 配置文件详解

文章目录一、log4j2介绍二、配置文件节点解析三、需要的Maven依赖四、最简配置五、较全面的配置补充一、log4j2介绍 log4j 2.x版本不再支持像1.x中的.properties后缀的文件配置方式&#xff0c;2.x版本配置文件后缀名只能为".xml",".json"或者".jsn&…

Redis radix tree源码解析

Redis实现了不定长压缩前缀的radix tree&#xff0c;用在集群模式下存储slot对应的的所有key信息。本文将详述在Redis中如何实现radix tree。 核心数据结构 raxNode是radix tree的核心数据结构&#xff0c;其结构体如下代码所示&#xff1a; typedef struct raxNode {uint32…

针对提高48V 配电性能的诸多思考!

作者&#xff1a;Phil Davies 众所周知配电网络 (PDN) 是所有电源系统的主干部分&#xff0c;但随着系统电源需求的不断上升&#xff0c;传统 PDN 承受着提供足够性能的巨大压力。 对于功耗和热管理而言&#xff0c;主要有两种方法可以改善 PDN 对电源系统性能的影响&#x…

Apache Cassandra static column 介绍与实战

假设我们有这样的场景&#xff1a;我们想在 Cassandra 中使用一张表记录用户基本信息&#xff08;比如 email、密码等&#xff09;以及用户状态更新。我们知道&#xff0c;用户的基本信息一般很少会变动&#xff0c;但是状态会经常变化&#xff0c;如果每次状态更新都把用户基本…