java.lang.IllegalArgumentException: Odd number of characters 字符长度奇数个异常
各位猿友可以自行尝试Hex.decode(),传入奇数个字符数组都会报错。
下面主要讲解引入shiro框架后出现此问题的缘由。
这个异常其实是Hex过程中抛出的异常,下面由我来分析一下起因经过
一般的,使用权限框架中为了加强密码强度,防止被恶意破解,我们会在使用加盐加密循环N次去强化密码,如下所示
/**
* 配置凭证匹配器
*/
@Bean
public CredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashIterations(3); // 加密次数
hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 算法名称 md5 sha
return hashedCredentialsMatcher;
}
看下HashedCredentialsMatcher构造器源码
public HashedCredentialsMatcher() {
this.hashAlgorithm = null;
this.hashSalted = false;
this.hashIterations = 1;
this.storedCredentialsHexEncoded = true;
}
注意关键字
storedCredentialsHexEncoded
这里shiro帮我们设置凭证匹配器的类型为hex,在看看下面源码
当我们进行用户密码登录校验的时候,shiro根据 AuthenticationToken 和 AuthenticationInfo 获取凭证进行比较校验的。
/**
* token,为登录时通过subject,login(xxx)传入的
* info,为自定义ralm中doGetAuthenticationInfo方法返回的对象
*
* tokenHashedCredentials,用户传入的用户密码进行加盐加密得到
* accountCredentials,数据库存储的密码(密文)
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = this.hashProvidedCredentials(token, info);
Object accountCredentials = this.getCredentials(info);
return this.equals(tokenHashedCredentials, accountCredentials);
}
protected Object getCredentials(AuthenticationInfo info) {
Object credentials = info.getCredentials();
byte[] storedBytes = this.toBytes(credentials);
if (credentials instanceof String || credentials instanceof char[]) {
if (this.isStoredCredentialsHexEncoded()) {
storedBytes = Hex.decode(storedBytes);
} else {
storedBytes = Base64.decode(storedBytes);
}
}
AbstractHash hash = this.newHashInstance();
hash.setBytes(storedBytes);
return hash;
}
在调用getCredentials方法的时候,因为我们配置的是hex方式,所以会执行
storedBytes = Hex.decode(storedBytes);
由于storedBytes长度为奇数个,所以在进行解码的时候报错了Odd number of characters。
但真正的问题应该是AuthenticationInfo的凭证有问题导致的,MD5加密后的密文,长度一定是偶数个。主要确保我们生成AuthenticationInfo时,传入的凭证为加密凭证就对了。
/**
* 自定义认证realm
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = String.valueOf(authenticationToken.getPrincipal());
UserEntity user = userService.getUser(username);
if (user == null) {
log.error("找不到用户信息,请重新登录");
return null;
}
// 生成AuthenticationInfo时,凭证应该为数据库中存储的密码
// SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, authenticationToken.getCredentials(), ByteSource.Util.bytes(username), getName());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, user.getPassword(), ByteSource.Util.bytes(username), getName());
return simpleAuthenticationInfo;
}
总结,如若出现以上问题,大概是对shiro认证环节不了解导致的。在认证过程中,程序员需要告诉shiro用户、密码和加密后的密码三个条件,shiro会在内部组装成两个对象进行校验的。