限制密码重试次数
# 1、实现原理
保证原子性:
单系统:AtomicLong计数
集群系统:RedissionClient提供的RAtomicLong计数
1、获取系统中是否已有登录次数缓存,缓存对象结构预期为:"用户名--登录次数"。
2、如果之前没有登录缓存,则创建一个登录次数缓存。
3、如果缓存次数已经超过限制,则驳回本次登录请求。
4、将缓存记录的登录次数加1,设置指定时间内有效
5、验证用户本次输入的帐号密码,如果登录登录成功,则清除掉登录次数的缓存
思路有了,那我们在哪里实现呢?我们知道AuthenticatingRealm里有比较密码的入口doCredentialsMatch方法
查看其实现
# 2、自定义密码比较器
新建项目shiro-day01-14shiro-RetryLimit
# 【1】RetryLimitCredentialsMatcher
package com.itheima.shiro.core.impl;
import com.itheima.shiro.core.base.ShiroUser;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* @Description:密码重试比较器
*/
public class RetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
private RedissonClient redissonClient;
private static Long RETRY_LIMIT_NUM = 4L;
/**
* @Description 构造函数
* @param hashAlgorithmName 匹配次数
* @return
*/
public RetryLimitCredentialsMatcher(String hashAlgorithmName,RedissonClient redissonClient) {
super(hashAlgorithmName);
this.redissonClient = redissonClient;
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//获得登录吗
String loginName = (String) token.getPrincipal();
//获得缓存
RAtomicLong atomicLong = redissonClient.getAtomicLong(loginName);
long retryFlag = atomicLong.get();
//判断次数
if (retryFlag>RETRY_LIMIT_NUM){
//超过次数设计10分钟后重试
atomicLong.expire(10, TimeUnit.MICROSECONDS);
throw new ExcessiveAttemptsException("密码错误5次,请10分钟以后再试");
}
//累加次数
atomicLong.incrementAndGet();
atomicLong.expire(10, TimeUnit.MICROSECONDS);
//密码校验
boolean flag = super.doCredentialsMatch(token, info);
if (flag){
//校验成功删除限制
atomicLong.delete();
}
return flag;
}
}
# 【2】重写ShiroDbRealmImpl
修改initCredentialsMatcher方法,使用RetryLimitCredentialsMatcher
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.SimpleCacheManager;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.*;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
/**
* @Description:自定义shiro的实现
*/
public class ShiroDbRealmImpl extends ShiroDbRealm {
@Autowired
private UserBridgeService userBridgeService;
@Autowired
private SimpleCacheManager simpleCacheManager;
@Resource(name = "redissonClientForShiro")
private RedissonClient redissonClient;
/**
* @Description 认证方法
* @param authcToken 校验传入令牌
* @return AuthenticationInfo
*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
SimpleToken token = (SimpleToken)authcToken;
User user = userBridgeService.findUserByLoginName(token.getUsername());
if(EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在");
}
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
String sessionId = ShiroUserUtil.getShiroSessionId();
String cacheKeyResourcesIds = CacheConstant.RESOURCES_KEY_IDS+sessionId;
shiroUser.setResourceIds(userBridgeService.findResourcesIdsList(cacheKeyResourcesIds,user.getId()));
String salt = user.getSalt();
String password = user.getPassWord();
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName());
}
/**
* @Description 授权方法
* @param principals SimpleAuthenticationInfo对象第一个参数
* @return
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
return userBridgeService.getAuthorizationInfo(shiroUser);
}
/**
* @Description 清理缓存
*/
@Override
public void doClearCache(PrincipalCollection principalcollection) {
String sessionId = ShiroUtil.getShiroSessionId();
simpleCacheManager.removeCache(CacheConstant.ROLE_KEY+sessionId);
simpleCacheManager.removeCache(CacheConstant.RESOURCES_KEY+sessionId);
simpleCacheManager.removeCache(CacheConstant.TOKEN+sessionId);
}
/**
* @Description 加密方式
*/
@Override
public void initCredentialsMatcher() {
RetryLimitCredentialsMatcher matcher = new RetryLimitCredentialsMatcher(SuperConstant.HASH_ALGORITHM,redissonClient);
matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
}
# 3、测试
访问http://127.0.0.1/shiro/login,使用admin账号输入错误密码5次
上次更新: 2025/03/09, 18:29:07