1. 修改CustomRealm.java中 doGetAuthenticationInfo 方法
之前我们会在里面进行密码的判断,现在我们把密码判断交给密码比较器
/**
* 登录查询
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException{
//获取用户名
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String userName = usernamePasswordToken.getUsername();
SysUser sysUser = new SysUser();
sysUser.setSysUserName(userName);
List<SysUser> sysUserList = sysUserService.queryAll(sysUser);
if(sysUserList.size() <= 0){
throw new UnknownAccountException("用户名不存在!");
}
int userId = sysUserList.get(0).getSysUserId();
String userStatus = sysUserList.get(0).getSysUserStatus();
String userPassword = sysUserList.get(0).getSysUserPassword();
if("1".equals(userStatus)){
throw new LockedAccountException("用户已锁定,请联系管理员!");
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userId,userPassword,getName());
return simpleAuthenticationInfo;
}
2. 新增RetryLimitHashedCredentialsMatcher.java
package com.pk.ass.config;
import com.pk.ass.entity.SysUser;
import com.pk.ass.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class RetryLimitHashedCredentialsMatcher extends SimpleCredentialsMatcher {
private static final Logger logger = LoggerFactory.getLogger(RetryLimitHashedCredentialsMatcher.class);
private final int ALLCOUNT = 5;
@Autowired
private SysUserService sysUserService;
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
this.passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String userName = token.getPrincipal().toString();
SysUser sysUser = new SysUser();
sysUser.setSysUserName(userName);
List<SysUser> sysUserList = sysUserService.queryAll(sysUser);
sysUser = sysUserList.get(0);
String userId = sysUser.getSysUserId().toString();
//获取缓存中的登录次数
AtomicInteger retryCount = passwordRetryCache.get(userId);
// 用户没有登录过,将用户添加到缓存中
if (retryCount == null) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(userId, retryCount);
}
// 登录次数大于5,锁定账号
if (retryCount.incrementAndGet() > ALLCOUNT) {
sysUser.setSysUserStatus("1");
sysUserService.update(sysUser);
logger.info("用户:{},锁定!", sysUser.getSysUserName());
//抛出用户锁定异常
throw new LockedAccountException("登录次数大于5次,已锁定账户,请联系管理员!");
}
// 判断用户账号和密码是否正确
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
passwordRetryCache.remove(userId);
} else {
int surplusCount = ALLCOUNT - passwordRetryCache.get(userId).intValue();
//抛出密码错误异常
throw new IncorrectCredentialsException("密码错误,总登录次数"+ALLCOUNT+"次,剩余次数: " + surplusCount);
}
return matches;
}
}
3. 在ShiroConfig.java增加
/**
* 配置密码比较器
* @return
*/
@Bean("credentialsMatcher")
public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher(){
RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
//如果密码加密,可以打开下面配置
//加密算法的名称
//retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5");
//配置加密的次数
//retryLimitHashedCredentialsMatcher.setHashIterations(1024);
//是否存储为16进制
//retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return retryLimitHashedCredentialsMatcher;
}
4. 在ShiroConfig.java的myShiroRealm方法中增加
//配置自定义密码比较器
customRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
//将自己的验证方式加入容器
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCachingEnabled(true);
//启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
customRealm.setAuthenticationCachingEnabled(true);
//缓存AuthenticationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
customRealm.setAuthenticationCacheName("authenticationCache");
//启用授权缓存,即缓存AuthorizationInfo信息,默认false
customRealm.setAuthorizationCachingEnabled(true);
//缓存AuthorizationInfo信息的缓存名称 在ehcache-shiro.xml中有对应缓存的配置
customRealm.setAuthorizationCacheName("authorizationCache");
//配置自定义密码比较器
customRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
return customRealm;
}
5. 在ehcache-shiro.xml中新增缓存区域
<!-- 登录失败次数缓存
注意 timeToLiveSeconds 设置为300秒 也就是5分钟
-->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
overflowToDisk="false"
statistics="true">
</cache>
6. 用户登录方法
@RequestMapping("login")
@ResponseBody
public Object login(String userName,String password,boolean rememberMe){
ResultEntity resultEntity = new ResultEntity();
Subject subject = SecurityUtils.getSubject();
password = password + userName;
String md5Str = DigestUtils.md5DigestAsHex(DigestUtils.md5DigestAsHex(password.getBytes()).getBytes());
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName,md5Str,rememberMe);
try{
subject.login(usernamePasswordToken);
}catch (Exception e){
resultEntity.setResult(false);
resultEntity.setMessage(e.getMessage());
return resultEntity;
}
resultEntity.setResult(true);
resultEntity.setMessage("登录成功");
return resultEntity;
}
7. 验证页面
参考文章
王赛超 - springboot整合shiro-登录失败次数限制(八)
饭一碗 - 原子操作类AtomicInteger详解