shrio自己内置一些密码校验规则,也可以实现简单的自定义,比如算法类型,hash次数等,但是有时候我们有一些比较特殊的密码校验规则,需要自定义来实现
1.shiro的密码校验是如何做的?
我们在登录方法内做完参数校验,验证码匹配等基本工作之后,都会将用户名和密码通过subject.login(auth) 传入框架来做用户匹配,但是,是和什么地方的数据进行匹配呢?
UsernamePasswordToken auth = new UsernamePasswordToken(username, password,false);
Subject subject = SecurityUtils.getSubject();
subject.login(auth);
答案就是在自定义的Realm中定义的AuthenticationInfo 进行匹配
可以通过shiro框架源码AuthenticatingRealm类的 assertCredentialsMatch方法看到,传入了两个参数,token就是我们登陆方法中传入的UsernamePasswordToken对象,而info就是我们在钉钉一Realm中doGetAuthenticationInfo 方法中定义的info对象。
shiro框架校验:
//shiro源码--用户信息认证 对比
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
//信息认证
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
这里需要弄清楚 doGetAuthenticationInfo和doGetAuthorizationInfo 前者是定义用户认证信息的,而后者是定义用户权限信息,有点容易混淆。
2.实现自定义Realm:
public class ShiroFileRealm extends AuthorizingRealm implements InitializingBean {
@Autowired
public void setCredentialsDigest(CredentialsDigest credentialsDigest) {
this.credentialsDigest = credentialsDigest;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = userService.getUser(token.getUsername());
if (user != null) {
//此处返回的对象就是上面的info
return new SimpleAuthenticationInfo(new ShiroUser(user.getUsername()), user.getPassword(),getName());
}
return null;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//自定义用户权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
/**
* 设定密码校验规则
*/
public void afterPropertiesSet() throws Exception {
CredentialsMatcher matcher = new CredentialsMatcherAdapter(credentialsDigest);
setCredentialsMatcher(matcher);
}
}
上面代码中SimpleAuthenticationInfo 就是返回的用户认证信息,也是框架中做对比的info
好了,认证流程基本弄明白,我们就需要实现自定义校验,怎么做呢?
3.实现自定义CredentialsMatcher 认证类
/**
* 设定密码校验规则
*/
public void afterPropertiesSet() throws Exception {
CredentialsMatcher matcher = new CredentialsMatcherAdapter(credentialsDigest);
//设置自定义的用户信息认证类
setCredentialsMatcher(matcher);
}
关键就是这里,CredentialsMatcherAdapter 是个实现了CredentialsMatcher接口的自定义类
public class CredentialsMatcherAdapter implements CredentialsMatcher {
private CredentialsDigest credentialsDigest;
public CredentialsMatcherAdapter(CredentialsDigest credentialsDigest) {
Assert.notNull(credentialsDigest, "The argument must not be null");
this.credentialsDigest = credentialsDigest;
}
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String plainCredentials, credentials;
byte[] saltByte = null;
plainCredentials = toStringCredentials(token.getCredentials());
if (info instanceof SaltedAuthenticationInfo) {
ByteSource salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
if (salt != null) saltByte = salt.getBytes();
}
credentials = toStringCredentials(info.getCredentials());
if(saltByte != null){
return credentialsDigest.matches(credentials, plainCredentials, saltByte);
}
return credentialsDigest.matches(credentials, plainCredentials);
}
private static String toStringCredentials(Object credentials) {
if (credentials == null) {
return null;
} else if (credentials instanceof String) {
return (String) credentials;
} else if (credentials instanceof char[]) {
return new String((char[]) credentials);
} else {
throw new IllegalArgumentException("credentials only support String or char[].");
}
}
}
doCredentialsMatch 方法就是实现用户信息校验 对比的方法,上面shiro框架的校验流程(cm.doCredentialsMatch(token, info))就会走到我们这里的方法中
上面credentialsDigest.matches(credentials, plainCredentials); 是我自己实现的密码比较方法
有加盐模式和无盐模式
贴出来给大家参考下
接口
public interface CredentialsDigest {
String digest(String plainCredentials, byte[] salt);
boolean matches(String credentials, String plainCredentials, byte[] salt);
boolean matches(String credentials, String plainCredentials);
}
实现类
public abstract class HashCredentialsDigest implements CredentialsDigest {
static final int HASH_ITERATIONS = 1024;
public String digest(String plainCredentials, byte[] salt) {
if (StringUtils.isBlank(plainCredentials)) return null;
byte[] hashPassword = digest(plainCredentials.getBytes(StandardCharsets.UTF_8), salt);
return Hex.encodeHexString(hashPassword);
}
public boolean matches(String credentials, String plainCredentials, byte[] salt) {
if (StringUtils.isBlank(credentials) && StringUtils.isBlank(plainCredentials)) return true;
return StringUtils.equals(credentials, digest(plainCredentials, salt));
}
public boolean matches(String credentials, String plainCredentials) {
if (StringUtils.isBlank(credentials) && StringUtils.isBlank(plainCredentials)) return true;
return StringUtils.equals(credentials, plainCredentials);
}
protected abstract byte[] digest(byte[] input, byte[] salt);
}
主要的自定义代码就在CredentialsMatcherAdapter和CredentialsDigest中。。。