Shiro最佳实践(五)自定义认证的盐值加密
Apache Shiroadmin 发布于:2019-08-03 23:34:48
阅读:loading
也许你有注意到了前面几篇文章的用户认证并未提及用户密码的加密方式,均是以明文的方式存储,然而实际上用户密码的存储必然不是明文存储的,这就是本文要描述的重点内容,实际上我们可以通过在ini配置文件中指定用户密码的加盐方式,然后与存储的用户密码密文进行对比验证,如果验证不一致均为失败,也可以在自定义认证实现中自行对密码加密校验,所以一个简单的加密验证方式如下(部分代码):
package cn.chendd.shiro.custom;
import ...
/**
* 自定义登录认证实现,示例使用不再继承IniRealm,省去一个ini的配置文件
*
* @author chendd
*/
public class LoginEncryptionRealm extends IniRealm {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public String getName() {
return "chendd-login-encryption-realm";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String userName = userToken.getUsername();
SysUserDao dao = new SysUserDao();
SysUser sysUser = dao.getUserByUserName(userName);
if (sysUser == null) {
throw new AuthenticationException("用户名或密码不匹配!");//实际上是用户不存在
}
//加密密码,使用用户名加盐并迭代2次
String password = this.encodePassword(new String(userToken.getPassword()), userName);
logger.debug("输入密码为:{},数据库密码为:{}", password, sysUser.getPassword());
if (!sysUser.getPassword().equals(password)) {
throw new AuthenticationException("用户名或密码不匹配!");
}
//构造授权认证对象返回
SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(userName,
userToken.getPassword(), ByteSource.Util.bytes(userName), this.getName());
return auth;
}
/**
* 得到加盐迭代两次后的密文
*/
private String encodePassword(Object source, String salt) {
Md5Hash md5 = new Md5Hash(source, salt, 2);
return md5.toHex();
}
}
上述代码中的encodePassword则是使用用户名为盐,并累计加密2次,Md5Hash为Shiro工具类,特别注意的是它的加盐执行多次后的结果与一次一次的加盐结果是不一样的。何谓加盐?其实就是将用户的原密码加上一个特殊的字符串再将他们进行MD5加密,同时将MD5加密后的密文再次加密,以此类推,找到指定的加密此时,所以这个加密N次后的密文真的不太好解,我感觉常见的盐可能会是系统默认的参数、用户名及用户注册时的验证码等。
在这里为了验证将N次盐值加密后的密文与一次一次累计加盐加密后的结果真的是不一致的,我特意将它们给整理出来,包括了直接调用Api中的盐值加密与一次一次累计加密和从Api中整理出来的加密实现代码,参考下列代码所示:
package cn.chendd.shiro.examples.md5;
import ...
/**
* @author chendd
* @date 2019/06/02 0:28
* 测试Shiro中Md5的加密实现,<b>多次迭代粗的密文与1次+1次...N次的结果是不一样的</b>
*/
public class Md5Test {
private String source = "cdd";//密文
private String salt = "admin";//加盐
private Integer hashIterations = 2;//迭代次数
/*
* 加盐加密的实现:cdd为密文,admin为加盐
* 1、使用1个参数的加盐加密的字符串累加;
* 2、使用2个参数的密文和加盐;
* 3、使用3个参数的密文与加盐各加盐1次;
*/
@Test
public void testMd5(){
//假设cdd为待加密字符,admin为盐,以下3种均结果相同
String md51 = new Md5Hash("admincdd" ).toString();
String result1 = new Md5Hash(md51 , "admin").toString();
System.out.println("1)加盐两次后的密文:" + result1);
//------
String md52 = new Md5Hash("cdd" , "admin" ).toString();
String result2 = new Md5Hash(md52 , "admin").toString();
System.out.println("2)加盐两次后的密文:" + result2);
//------
String md53 = new Md5Hash("cdd" , "admin" ,1).toString();
String result3 = new Md5Hash(md53 , "admin",1).toString();
System.out.println("3)加盐两次后的密文:" + result3);
}
/**
* 多次加盐加密的迭代实现:基于源码整理
*/
@Test
public void testMd5SaltHashIterationsSource() throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte saltByte[] = ByteSource.Util.bytes(salt).getBytes();
digest.reset();
digest.update(saltByte);
byte[] hashed = digest.digest(source.getBytes());
int iterations = hashIterations - 1; //already hashed once above
for (int i = 0; i < iterations; i++) {
digest.reset();
hashed = digest.digest(hashed);
}
System.out.println("基于源码整理的多次加盐迭代结果:" + Hex.encodeToString(hashed));
}
/**
* 加盐加密的多次迭代实现:API直接调用
*/
@Test
public void testMd5SaltHashIterationsAPI(){
Md5Hash md5 = new Md5Hash(source , salt , hashIterations);
System.out.println("基于API调用的多次加盐迭代结果:" + md5.toHex());
}
}
点赞
发表评论
当前回复:作者