男男床做视频网站,贵州企业品牌网站建设,做网站模板哪里买,wordpress如何修改版权文章目录一、尝试登录次数控制实现1. 实现原理2. maven依赖3. ehcache配置4. RetryLimitCredentialsMatcher5. Shiro配置修改6. realm添加认证器7. 并发在线人数控制实现8. ehcache配置9. shiro配置10. shiro过滤链中加入并发登录人数过滤器11. 源码地址12. 参考博客一、尝试登…
文章目录一、尝试登录次数控制实现1. 实现原理2. maven依赖3. ehcache配置4. RetryLimitCredentialsMatcher5. Shiro配置修改6. realm添加认证器7. 并发在线人数控制实现8. ehcache配置9. shiro配置10. shiro过滤链中加入并发登录人数过滤器11. 源码地址12. 参考博客一、尝试登录次数控制实现
1. 实现原理
Realm在验证用户身份的时候要进行密码匹配。最简单的情况就是明文直接匹配然后就是加密匹配这里的匹配工作则就是交给CredentialsMatcher来完成的。我们在这里继承这个接口自定义一个密码匹配器缓存入键值对用户名以及匹配次数若通过密码匹配则删除该键值对若不匹配则匹配次数自增。超过给定的次数限制则抛出错误。这里缓存用的是ehcache。
2. maven依赖
shiro-ehcache配置
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!-- mybatis-plus依赖 --dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.1/version/dependency!-- 引入thymelaf 则不需要引入web依赖若不需要thymelaf则需要添加spring-boot-starter-web --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependency!-- shiro--dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-spring/artifactIdversion1.7.0/version/dependencydependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-ehcache/artifactIdversion1.7.0/version/dependencydependencygroupIdcom.github.penggle/groupIdartifactIdkaptcha/artifactIdversion2.3.2/version/dependency3. ehcache配置
?xml version1.0 encodingUTF-8?
ehcache nameesdiskStore pathjava.io.tmpdir/!--name:缓存名称。maxElementsInMemory:缓存最大数目maxElementsOnDisk硬盘最大缓存个数。eternal:对象是否永久有效一但设置了timeout将不起作用。overflowToDisk:是否保存到磁盘当系统当机时timeToIdleSeconds:设置对象在失效前的允许闲置时间单位秒。仅当eternalfalse对象不是永久有效时使用可选属性默认值是0也就是可闲置时间无穷大。timeToLiveSeconds:设置对象在失效前允许存活时间单位秒。最大时间介于创建时间和失效时间之间。仅当eternalfalse对象不是永久有效时使用默认是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的三种清空策略;FIFOfirst in first out这个是大家最熟的先进先出。LFU Less Frequently Used就是上面例子中使用的策略直白一点就是讲一直以来最少被使用的。如上面所讲缓存的元素有一个hit属性hit值最小的将会被清出缓存。LRULeast Recently Used最近最少使用的缓存的元素有一个时间戳当缓存容量满了而又需要腾出地方来缓存新的元素的时候那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。--defaultCachemaxElementsInMemory10000eternalfalsetimeToIdleSeconds120timeToLiveSeconds120overflowToDiskfalsediskPersistentfalsediskExpiryThreadIntervalSeconds120/!-- 登录记录缓存锁定10分钟 --cache namepasswordRetryCachemaxEntriesLocalHeap2000eternalfalsetimeToIdleSeconds3600timeToLiveSeconds0overflowToDiskfalsestatisticstrue/cache!-- 用户队列缓存10分钟 --cache nameshiro-kickout-sessionmaxEntriesLocalHeap2000eternalfalsetimeToIdleSeconds3600timeToLiveSeconds0overflowToDiskfalsestatisticstrue/cache/ehcache4. 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) {CacheString, 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 CacheString, DequeSerializable 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);//读取缓存 没有就存入DequeSerializable deque cache.get(username);if (deque null) {deque new LinkedListSerializable();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 nameshiro-kickout-sessionmaxEntriesLocalHeap2000eternalfalsetimeToIdleSeconds3600timeToLiveSeconds0overflowToDiskfalsestatisticstrue/cache9. 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