加密
密码保存在数据库中肯定不能明文保存,如果被获取或着拖库,用户账号信息就很危险了。之前想过用MD5加密,后来听说MD5使用彩虹表也能倒推出明文,而且像要让不同用户的相同密码在数据库中也不同,就要加上盐值了。
代码示例
在密码验证阶段,我使用了一个main函数求出了以用户名加密后的值作为盐值的密码密文:
public static void main(String[] args) {
// 用户名具有唯一性,所以其作为盐值的原始值
ByteSource salt = ByteSource.Util.bytes("admin");
//加密1024次
int hashIterations = 1024;
/*
对应于applicationContext中得配置
shiro.xml 中 storedCredentialsHexEncoded=true 则需要 .toHex() 就是原始值
shiro.xml 中 storedCredentialsHexEncoded=false 则需要 .toBase64()
*/
String hashPw = new SimpleHash("MD5", "123", salt, hashIterations).toHex();
System.out.println(hashPw);
}
在认证方法中模拟出从数据库中获取密码密文的过程带上盐值一起返回,交给shiro框架与前台传进来的信息做比对。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
System.out.println("do authentic");
if ("root".equals(token.getUsername())) {
//使用用户名作为盐值(都是模拟从数据库中取出来的,下面密码也是。还有盐值也是,作为解密用)盐和密码一起存入数据库
ByteSource salt = ByteSource.Util.bytes(token.getUsername());
//密码时hash后的
return new SimpleAuthenticationInfo("root", "b1ba853525d0f30afe59d2d005aad96c", salt, getName());
} else if ("admin".equals((token.getUsername()))) {
ByteSource salt = ByteSource.Util.bytes(token.getUsername());
//admin密码时123
return new SimpleAuthenticationInfo("admin", "c41d7c66e1b8404545aa3a0ece2006ac", salt, getName());
}
return null;
}
前台传来的是明文,如何和密文比对呢,shiro需要知道的是加密算法、加密次数,配合realm中传回的密文和盐值才可完成匹配,所以我们要告诉shiro加密信息,在applicationContext.xml文件中加上属性credentialsMatcher设置加密算法属性和加密次数属性,然后最后一个属性告诉匹配器是用hex还是base64:
<bean id="myRealm" class="realm.MyRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法名称-->
<property name="hashAlgorithmName" value="MD5"/>
<!--重复加密次数-->
<property name="hashIterations" value="1024"/>
<!--是否存储散列后的密码为16进制,为 true:.toHex(),为 false:.toBase64()-->
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
</property>
</bean>
这样就完成了代码,测试也能通过。
流程分析
- 认证流程前面基本一样无非subject调用securityManager等等,详情看(一)
-
直到MyRealm返回密文和盐值到AuthenticatingRealm中,开始比对。
-
判断属性credentialsMatcher是否为空,因为我们配置文件中有配置,所以不为空。
-
进入到credentialsMatcher的doCredentialsMatch方法中进行校验:
此方法有三步,第一步对前台传进来的信息加密,第二部获取数据库密文,第三部两个密文对比,返回验证信息。下面分别看三部流程:
-
加密前台信息:
(1)首先判断MyRealm传来的是不是包含盐值的SaltedAuthenticationInfo类,是则获取盐值。
(2)然后获取加密算法字段:
(3)最后获取加密次数:
是直接从属性中获取,加上这一步会更具体。
(4)加密
使用前文main方法中相同的加密方式:
- 获取数据库中密文,直接info.getCredentials()即可。
-
比对密码
转换成字节后比对:
- 后面也和(一)中一样,不多描述。
网友评论