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中。。。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论