考虑系统密码的安全,目前大多数系统都不会把密码以明文的形式存放到数据库中。
一把会采取以下几种方式对密码进行处理
密码的存储
“编码”存储
Shiro 提供了 base64和 16 进制字符串编码/解码的 API支持,方便一些编码解码操作。 Shiro内部的一些数据的存储/表示都使用了 base64和 16 进制字符串。
下面两端代码分别对其进行演示
Stringstr = "hello";
Stringbase64Encoded = Base64.encodeToString(str.getBytes());
Stringstr2 = Base64.decodeToString(base64Encoded);
Assert.assertEquals(str,str2);
通过如上方式可以进行 base64编码/解码操作
Stringstr = "hello";
Stringbase64Encoded = Hex.encodeToString(str.getBytes());
Stringstr2 =newString(Hex.decode(base64Encoded.getBytes()));
Assert.assertEquals(str,str2);
通过如上方式可以进行 16 进制字符串编码/解码操作。
Hash存储
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA等。一般进行散列时最好提供一个 salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和 ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
Stringstr = "hello";
Stringsalt = "123";
Stringmd5 =new Md5Hash(str, salt).toString();//还可以转换为 toBase64()/toHex()
如上代码通过盐“123”MD5散列“hello”。另外散列时还可以指定散列次数,如 2次表示:md5(md5(str)):“new Md5Hash(str, salt, 2).toString()”
Stringstr = "hello";
Stringsalt = "123";
Stringsha1 =new Sha256Hash(str, salt).toString();
使用 SHA256 算法生成相应的散列数据,另外还有如 SHA1、SHA512算法。
Shiro 还提供了通用的散列支持:
Stringstr = "hello";
Stringsalt = "123";
//内部使用MessageDigest
StringsimpleHash =new SimpleHash("SHA-1", str, salt).toString();
通过调用 SimpleHash 时指定散列算法,其内部使用了 Java的 MessageDigest 实现。为了方便使用,Shiro提供了 HashService,默认提供了 DefaultHashService实现
DefaultHashServicehashService =new DefaultHashService(); //默认算法 SHA-512
hashService.setHashAlgorithmName("SHA-512");
hashService.setPrivateSalt(newSimpleByteSource("123"));//私盐,默认无
hashService.setGeneratePublicSalt(true);//是否生成公盐,默认false
hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());//用于生成公盐。默认就这个
hashService.setHashIterations(1);//生成 Hash 值的迭代次数
HashRequestrequest =new HashRequest.Builder()
.setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello"))
.setSalt(ByteSource.Util.bytes("123")).setIterations(2).build();
Stringhex =hashService.computeHash(request).toHex();
加密存储
Shiro 还提供对称式加密/解密算法的支持,如 AES、Blowfish等;当前还没有提供对非对称加密/解密算法支持,未来版本可能提供。
AES 算法实现:
AesCipherServiceaesCipherService =new AesCipherService();
aesCipherService.setKeySize(128);//设置 key 长度
//生成 key
Keykey=aesCipherService.generateNewKey();
Stringtext = "hello";
//加密
StringencrptText =
aesCipherService.encrypt(text.getBytes(),key.getEncoded()).toHex();
//解密
Stringtext2 =
newString(aesCipherService.decrypt(Hex.decode(encrptText),key.getEncoded()).getBytes());
Assert.assertEquals(text, text2);
密码的验证
使用“密码的存储”章节中其中一种存储方式存储密码之后,接下来就是登陆时密码的验证.
该过程主要涉及到两个问题:
(1) 让Shiro知道数据库中存储的密码是通过什么方式存储的?
可以在指定比较器时进行设置
(2) 让Shiro知道如何比对数据库与登录时的密码?
Shiro主要通过CredentialsMatcher的子类来实现密码的对比,Shiro已经实现了比较常见的Matcher,例如Md5CredentialsMatcher,Sha256CredentialsMatcher等等,如果Shiro提供的Matcher不能满足需求,还可以自定义Matcher.
Spring集成Shiro密码验证实例
本小节以Spring集成Shiro为基础,介绍如何使用MD5对密码进行加密,添加密码加密功能需要经过以下三步
保存密码
作为密码的数据源头,首先要确保在密码保存时就使用MD5加密
在Spring的Controller对应的方法中,使用以下方法保存密码
@RequestMapping("/setting/user/add")
public String addUser(@Valid User user, BindingResult result, Map<String, Object> model,
@RequestParam(value = "action") String action) {
String cryptedPwd = new Md5Hash(user.getPwd()).toString();
user.setPwd(cryptedPwd);
userService.saveUser(user);
return "redirect:/setting/user/" + user.getName();
}
配置Matcher
当数据库中存的是MD5形式的密码后,就要告诉shiro如何对登陆时输入的密码进行对比,在这里采用默认的HashedCredentialsMatcher作为比较器
Shiro-config.xml配置如下
<!—在自定义的Realm中配置Matcher,并指定加密算法-->
<beanid="customerRealm" class="com. test.security.CustomerRealm">
<propertyname="credentialsMatcher">
<beanclass="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<propertyname="hashAlgorithmName" value="MD5" />
</bean>
</property>
</bean>
密码校验
对于CustomerRealm而言,不需要关心密码是如何被匹配的,只需要将用户输入的密码传入AuthenticationInfo对象中即可
@Override
protectedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
// 获取基于用户名和密码的令牌,authcToken是从LoginController里面//currentUser.login(token)传过来的
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = userService.findUserByName(token.getUsername());
if(null != user) {
AuthenticationInfo authcInfo =
new SimpleAuthenticationInfo(user.getName(), user.getPwd(),user.getName());
this.setSession(GCloudConstant.CURRENT_USER,user.getName());
return authcInfo;
}else {
return null;
}
}
来源:oschina
链接:https://my.oschina.net/u/1425545/blog/543501