这是一篇笔记,会随时跟着shiro的学习更新
关于Shrio自带的Exception使用
在用户登录的时候会有这样的判断
String username = users.getUserName();
logger.info("用户paswd为:"+users.getPassword());
UsernamePasswordToken token = new UsernamePasswordToken(username, users.getPassword(),true);
Subject currentUser = SecurityUtils.getSubject();
try {
logger.info("对用户[" + username + "]进行登录验证..验证开始");
currentUser.login(token);
logger.info("对用户[" + username + "]进行登录验证..验证通过");
}catch(UnknownAccountException uae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户or state=0");
model.addAttribute("error", "无此账户/未激活");
}catch(IncorrectCredentialsException ice){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
model.addAttribute("error", "密码不正确");
}catch(LockedAccountException lae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
model.addAttribute("error", "账户已锁定");
}catch(ExcessiveAttemptsException eae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
model.addAttribute("error", "用户名或密码错误次数过多");
}catch(AuthenticationException ae){
logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
model.addAttribute("error", "用户名或密码不正确");
ae.printStackTrace();
}
这里有很多exception,其中每一个logger.info的描述很清楚。
但是如何使用呢。
我们日常验证的时候都是在ShrioRealm丢参来判定登录和权限。
假设,我们目前user表中。有一个Status来代表状态。1代表不被锁定账号。其他都是。
那么可以这么用
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
logger.info("验证当前Subject时获取到token为:" + token.toString());
User user = userService.selectAllByName(token.getUsername());
if (user != null) {
Object credentials = user.getPassword();
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUserName());
//判定管理员有没有锁
if(userMapper.selectStatus(user.getUserName())!=1){
throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ");
}
return new SimpleAuthenticationInfo(user, credentials, credentialsSalt, getName());
}
return null;
}
在这里,我添加了
if(userMapper.selectStatus(user.getUserName())!=1){
throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ");
}
这一个判断语句。
从userMapper映射中获取对于UserName的那一列来得出status的值。
所以。我们在AuthenticationInfo预先处理这个status字段。然后通过
throw new LockedAccountException("账号:"+user.getUserName()+" 已经被锁定 ")
来得出让我们的从前端输入的username值,直接被锁。
输入次数过多,锁几分钟
根据这个标题,我们可以对照以下我最早放在上面的代码得出是那个Exception。
也就是
ExcessiveAttemptsException
那么怎么使用呢。
分析一下。在shiro配置文件添加RetryLimitHashedCredentialsMatcher。然后实现RetryLimitHashedCredentialsMatcher。最后在判定登录的请求中判断输入的次数,然后添加对它的引用。其中。我们需要用到ehcache来存入错误的次数和等待的时间
第一步
在ShiroConfiguration.java中添加
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
重点是第一行。
然后需要在需要在realm配置器中添加
@Bean(name = "shiroRealm")
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRealm shiroRealm() {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
然后ehcache
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
public EhCacheManager ehCacheManager() {
logger.debug(
"=====shiro整合ehcache缓存:ShiroConfiguration.getEhCacheManager()");
EhCacheManager ehcache = new EhCacheManager();
CacheManager cacheManager = CacheManager.getCacheManager("shiro");
if(cacheManager == null){
try {
cacheManager = CacheManager.create(ResourceUtils.getInputStreamForPath("classpath:config/ehcache.xml"));
} catch (CacheException | IOException e) {
e.printStackTrace();
}
}
ehcache.setCacheManager(cacheManager);
return ehcache;
}
@Bean public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(shiroRealm());
// //注入ehcache缓存管理器;
securityManager.setCacheManager(ehCacheManager());
// //注入session管理器;
securityManager.setSessionManager(sessionManager());
//注入Cookie记住我管理器
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//sessionManager.setCacheManager(ehCacheManager());
sessionManager.setSessionDAO(enterCacheSessionDAO());
sessionManager.setSessionIdCookie(sessionIdCookie());
return sessionManager;
}
@Bean
public EnterpriseCacheSessionDAO enterCacheSessionDAO() {
EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
//添加缓存管理器
//enterCacheSessionDAO.setCacheManager(ehCacheManager());
//添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)
enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
return enterCacheSessionDAO;
}
第二步
创建RetryLimitHashedCredentialsMatcher.java
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
//集群中可能会导致出现验证多过5次的现象,因为AtomicInteger只能保证单节点并发
//解决方案,利用ehcache、redis(记录错误次数)和mysql数据库(锁定)的方式处理:密码输错次数限制; 或两者结合使用
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
//读取ehcache中配置的登录限制锁定时间
passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
/**
* 在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)中进行身份认证的密码匹配,
* </br>这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,
* </br>从而实现了如果登录次数超出指定的值就锁定。
* @param token
* @param info
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token,
AuthenticationInfo info) {
//获取登录用户名
String username = (String) token.getPrincipal();
//从ehcache中获取密码输错次数
// retryCount
//判定管理员有没有锁
AtomicInteger retryCount = passwordRetryCache.get(username);
if (retryCount == null) {
//第一次
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
//retryCount.incrementAndGet()自增:count + 1
if (retryCount.incrementAndGet() > 5) {
// if retry count > 5 throw 超过5次 锁定
throw new ExcessiveAttemptsException("账号:"+username+" 密码超过5次了,请等待6分钟");
}
//否则走判断密码逻辑
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
// clear retry count 清楚ehcache中的count次数缓存
passwordRetryCache.remove(username);
}
return matches;
}
}
第三步
在login判断中写入
接最上面的try catch
Cache<String, AtomicInteger> passwordRetryCache = ecm
.getCache("passwordRetryCache");
if (null != passwordRetryCache) {
int retryNum = (passwordRetryCache.get(users.getUserName()) == null ? 0
: passwordRetryCache.get(users.getUserName())).intValue();
logger.debug("输错次数:" + retryNum);
if (retryNum > 0 && retryNum < 6) {
model.addAttribute("error", "用户名或密码错误" + retryNum + "次,再输错"
+ (6 - retryNum) + "次账号将锁定");
}
}
//验证是否登录成功
if(currentUser.isAuthenticated()){
logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
LoginLog loginLog = new LoginLog();
loginLog.setUsername(username);
loginLog.setSystemBrowserInfo();
loginLogService.saveLoginLog(loginLog);
......省略
}