前文中已经实现了基本的权限控制,使用的是shiro默认提供的密码比较器,但是在有些情况下,可能需要一些自定义。比如说,想使用自己的加密方式,再者使用ladp进行用户认证等等。
一、查看源码
查看HashedCredentialsMatcher的继承结构可以看出,他是继承自SimpleCredentialsMatcher,也就是说只要继承SimpleCredentialsMatcher,重写doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)这个方法即可。
二、具体实现
package com.wangcongming.crm.config.shiro;
import com.wangcongming.crm.rao.RedisRao;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Objects;
/**
* 自定义密码比较规则
* @author Administrator
*
*/
public class CredentialsMatcher extends SimpleCredentialsMatcher{
@Autowired
private RedisRao redisRao;
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken=(UsernamePasswordToken) token;
//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String inPassword = new String(utoken.getPassword());
String username = utoken.getUsername();
//获得数据库中的密码
String dbPassword = (String) info.getCredentials();
SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
// ByteSource salt = saInfo.getCredentialsSalt();
ByteSource salt = ByteSource.Util.bytes(username);
inPassword = new SimpleHash("sha-1", //加密方式
inPassword,//密码原值
salt,//盐值
916//加密次数
).toString();
//进行密码的比对
boolean flag = Objects.equals(inPassword, dbPassword);
return flag;
}
}
这里可以看出只是重写了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)这个方法。在这里可以自定义密码验证,返回值是boolean类型,返回true则是密码比较通过,验证通过,可以成功登录,返回false则是密码校验失败,登录失败
这里只是编码完成了,还需要修改配置文件
<!-- 密码验证器 -->
<bean id="credentialsMatcher" class="com.wangcongming.crm.config.shiro.CredentialsMatcher"></bean>
<!-- 授权 认证 -->
<bean id="NDShiroRealm" class="com.wangcongming.crm.config.shiro.NDShiroRealm" >
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
三、密码输入错误多次锁住用户功能实现
在很多网站中都可以看到,密码输入次数过多,将会锁住用户一段时间,这样也是为了保护用户,防止账号被盗,具体实现如下:
- 修改realm域
@Autowired
private UserDao userDao;
@Autowired
private RedisRao redisRao;
public static final String USER_LOGIN_COUNT = "crm:login:count:";//用户登录次数计数
public static final String USER_IS_LOCK = "crm:login:lock:";//用户登录是否被锁定 一小时
public static final String USER_LOCK = "LOCK";
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
* @param authcToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
//访问一次,计数一次
String key = USER_LOGIN_COUNT + name;
long loginCount = redisRao.increment(key, 1L);
long expire = redisRao.getExpireStr(key);
if(expire == -1 || expire == -2){
redisRao.setExpireStr(key, 1L, TimeUnit.HOURS);
}
logger.info("用户:{}密码错误次数:{}",name,loginCount);
//计数大于5时,设置用户被锁定一小时
String isLockKey = USER_IS_LOCK + name;
if(loginCount > 5){
logger.info("密码错误5次,用户锁定");
redisRao.setString(isLockKey, USER_LOCK);
redisRao.setExpireStr(isLockKey, 1, TimeUnit.HOURS);
}
if (USER_LOCK.equals(redisRao.getString(isLockKey))){
throw new DisabledAccountException("密码输入错误次数大于5次,请一个小时之后重试!");
}
User user = userDao.findUserByUsername(name);
if (null == user) {
throw new AccountException("帐号或密码不正确!");
}
if("0".equals(user.getStatus())){
throw new DisabledAccountException("此帐号已经被禁止登录,请联系管理员");
}
ByteSource salt = ByteSource.Util.bytes(name);
return new SimpleAuthenticationInfo(user, user.getPassword(),salt, getName());
}
只需要修改登录认证这一块即可
- 修改密码比较器
package com.wangcongming.crm.config.shiro;
import com.wangcongming.crm.rao.RedisRao;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Objects;
/**
* 自定义密码比较规则
* @author Administrator
*
*/
public class CredentialsMatcher extends SimpleCredentialsMatcher{
@Autowired
private RedisRao redisRao;
public static final String USER_LOGIN_COUNT = "crm:login:count:";//用户登录次数计数
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken=(UsernamePasswordToken) token;
//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String inPassword = new String(utoken.getPassword());
String username = utoken.getUsername();
//获得数据库中的密码
String dbPassword = (String) info.getCredentials();
SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
// ByteSource salt = saInfo.getCredentialsSalt();
ByteSource salt = ByteSource.Util.bytes(username);
inPassword = new SimpleHash("sha-1", //加密方式
inPassword,//密码原值
salt,//盐值
916//加密次数
).toString();
//进行密码的比对
boolean flag = Objects.equals(inPassword, dbPassword);
if(flag){
//密码正确
String key = USER_LOGIN_COUNT + username;
redisRao.deleteStr(key);
}
return flag;
}
}
实现思路就是,在realm登录认证的时候,判断登录次数是否达到锁住用户的值,没有达到则记录一次登录次数,并且设置有效时间为一小时,然后每次+1操作,在密码比较器中,密码比较成功之后,将登陆失败记录次数删除即可