主要就是用来限制用户登录尝试次数的,登陆失不失败,与密码认证有关,所以要自定义一个密码匹配器,继承原来的HashedCredentialsMatcher密码匹配器,重写验证方法doCredentialsMatch。本文在上一篇文章Shiro功能应用(五)–Session管理的登陆人数控制代码基础进行添加登陆次数限制。
代码实现:
代码地址:
https://github.com/OooooOz/SpringBoot-Shiro
ShiroConfig的自定义Realm注入自定义密码比较器:
@Bean(name="shiroRealm")
public MyShiroRealm getMyShiroRealm(@Qualifier("credentialsMatcherLimit")HashedCredentialsMatcher credentialsMatcher){
MyShiroRealm shiroRealm = new MyShiroRealm();
//shiroRealm.setCredentialsMatcher(credentialsMatcher);//设置密码比较器,beanId=credentialsMatcher
shiroRealm.setCredentialsMatcher(credentialsMatcher);//设置自定义密码比较器,beanId=credentialsMatcherLimit
... ... ... ...
return shiroRealm;
}
ShiroConfig的自定义密码比较器:
@Bean("credentialsMatcherLimit")
public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher(){
RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(getEhCacheManager());
retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5"); //加密算法的名称
retryLimitHashedCredentialsMatcher.setHashIterations(1); //配置加密的次数
retryLimitHashedCredentialsMatcher.setRetryLimitNum(3); //配置加密的次数
//retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//是否存储为16进制
return retryLimitHashedCredentialsMatcher;
}
自定义密码比较器类:
package com.demo.config;
import java.util.concurrent.atomic.AtomicInteger;
import com.demo.dao.IUserMapper;
import com.demo.entity.User;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @description: 登陆次数限制
*/
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
@Autowired
private IUserMapper userMapper;
private Cache<String, AtomicInteger> passwordRetryCache;
private int retryLimitNum;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
public void setRetryLimitNum(int retryLimitNum) {
this.retryLimitNum = retryLimitNum;
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//获取用户名
String username = (String)token.getPrincipal();
//获取用户登录次数
AtomicInteger retryCount = passwordRetryCache.get(username);
if (retryCount == null) {
//如果用户没有登陆过,登陆次数加1 并放入缓存
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
if (retryCount.incrementAndGet() >retryLimitNum) {
//如果用户登陆失败次数大于retryLimitNum, 抛出锁定用户异常 并修改数据库字段
User user = userMapper.findByUserName(username);
if (user != null && 0==user.getStatus()){
//数据库字段 默认为 0 就是正常状态 所以 要改为1
//修改数据库的状态字段为锁定
user.setStatus(1);
userMapper.update(user);
}
System.out.println("锁定用户" + user.getUserName());
//抛出用户锁定异常
throw new LockedAccountException();
}
//判断用户账号和密码是否正确
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
//如果正确,从缓存中将用户登录计数 清除
passwordRetryCache.remove(username);
}
return matches;
}
/**
* 根据用户名 解锁用户
*/
public void unlockAccount(String username){
User user = userMapper.findByUserName(username);
if (user != null){
//修改数据库的状态字段为锁定
user.setStatus(0);
userMapper.update(user);
passwordRetryCache.remove(username);
}
}
}
EHCache缓存配置文件:
<!-- 登录失败次数缓存
注意 timeToLiveSeconds 设置为300秒 也就是5分钟
可以根据自己的需求更改
-->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
overflowToDisk="false"
statistics="true">
</cache>
执行过程:
在Shiro内部认证方法中,执行doCredentialsMatch会跳转到自定义密码匹配器的方法。使用了EHCache(构造方法注入的new RetryLimitHashedCredentialsMatcher(getEhCacheManager());)
第一次登陆,缓存无数据,retryCount=null,初始化次数=0,然后将次数put进缓存,在调用父类的super.doCredentialsMatch(token, info); 时,如果通过认证,则将之前放入的缓存移除调,如果认证失败,则retryCount会自动加1,(retryCount为AtomicInteger类型),此时为第一次登陆失败。
如果登陆次数大于指定次数,则将用户状态更新,然后抛出用户锁定异常。缓存设置类300s失效,所以300s内是无法登陆的,300s后才能登陆。
在控制器中捕获异常,输出异常信息到前台
参考文章:
springboot整合shiro-登录失败次数限制(八)