相信你已经知道,在实际项目中,用户的密码在数据库中一定是密文。比如你的CSDN帐号的密码是123456,但是在CSDN的数据库表中,可能存放的就是类似"e389b243168cd658ccae899240bf8321"这样的一段密文。
为了快速上手,我们需要知道一些加密的算法。常见的加密算法有MD5。该如何使用呢?Shiro提供了一套加密API。请看下面代码的演示。
@Test
public void testPassword(){
String password = "123456";
Md5Hash md5Hash = new Md5Hash(password);
//打印结果:e10adc3949ba59abbe56e057f20f883e
System.out.println(md5Hash);
}
你的密码123456,被加密成了e10adc3949ba59abbe56e057f20f883e。你觉得已经很安全了吗?很不幸,MD5算法可能会被破解。一般进行加密时最好提供一个salt(盐),我们可以加一些只有系统知道的干扰数据,这个盐值可以随意自定,比如加上你的名字(即盐)。这样散列的对象是“密码+ 盐”,这样生成的散列值相对来说更难破解。
好的,既然要加干扰数据,那么就来点干脆的,加上独一无二的名字好了。此时盐(salt)值就是 csdnName,将其作为Md5Hash的构造参数一起传递进去。
@Test
public void testPassword2(){
String password = "123456";
String csdnName = "小大宇";
//第二个参数就是 盐salt
Md5Hash md5Hash = new Md5Hash(password,csdnName);
//打印结果:6e588f84bc14b24845f9d2859665e4e5
System.out.println(md5Hash);
}
通常情况下,为了保险起见,还会进行循环加密。这里将加密后的“6e588f84bc14b24845f9d2859665e4e5”视为明文,用之前相同的加密手段,再循环加密3次,那么这个密码的破解难度可想而知。
@Test
public void testPassword3(){
String password = "123456";
String csdnName = "小大宇";
Md5Hash md5Hash = new Md5Hash(password,csdnName, 3);
//打印结果:e36e406088fcbe05c70ec1ab33bffa64
System.out.println(md5Hash);
}
首先,毋庸置疑的是,在你的真实项目中,插入用户密码的时候,需要先进行加密处理,再插入数据库的表。在验证用户密码的时候,再使用相同的加密算法计算用户输入的密码。
好的,那我们的Realm应该怎么弄呢?
首先,我们先创建出来这个Realm,依旧继承我们之前讨论的这个AuthorizingRealm类,并重写doGetAuthenticationInfo方法。
public class PasswordRealm extends AuthorizingRealm ...
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String clientUsername = (String) token.getPrincipal();
String yourInputPasswordFromWeb = new String((char[]) token.getCredentials());
return null;
}
好了,你可以像上面一样得到用户输入的明文密码。但是应该怎么才能将其变成密文呢?
这个时候,你要为Shiro提供你的加密算法。我们之前讨论的加密算法是: 使用 md5(密码 + 你的名字 ) 循环加密3次。所以,我们得把这个加密算法告诉我们的Realm。Shiro也提供了一个接收用户加密算法的组件。传说这个组件的名字叫:HashedCredentialsMatcher。
OK,下一步,我们得为我们的Realm提供这个加密算法。你可以使用构造函数注入。就像下面一样。
public class PasswordRealm extends AuthorizingRealm ...
public PasswordRealm() {
//采用md5算法
HashedCredentialsMatcher passwordMatcher = new HashedCredentialsMatcher("md5");
//循环加密3次
passwordMatcher.setHashIterations(3);
//再将这个加密组件注入到我们的Realm中
this.setCredentialsMatcher(passwordMatcher);
}
你也可以将这个加密组件的注入放在shiro.ini配置文件中,不过现在你不用关心这种方式。
细心的你可能会发现:咦,说好的加密的是"密码 + 你的名字",那该在哪里定义这些数据呢?OK,不着急,我们看代码。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String clientUsername = (String) token.getPrincipal();
String yourExpectedSalt = "小大宇";
return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
generateSalt(yourExpectedSalt), getName());
}
private String getPasswordFromDB(String clientUsername) {
//模拟从数据库查出来的密文
return "e36e406088fcbe05c70ec1ab33bffa64";
}
private ByteSource generateSalt(String salt) {
//将你指定的盐做内部处理
return ByteSource.Util.bytes(salt);
}
看到了吗,我们这次返回的凭证居然有四个参数。直接返回一个携带着相关参数的凭证info,再将其委托给Shiro去验证。程序员再次光荣的甩出任务。呵,程序员。
clientUsername:用户输入的帐号
getPasswordFromDB(clientUsername) :通过用户输入的帐号,查询数据库,返回一串密文
generateSalt(yourExpectedSalt):你指定什么作为盐呢?本篇博客用的盐就是用户的名字,于是我就定了它是“小大宇”
getName():当前Realm的名字。
return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
generateSalt(yourExpectedSalt), getName());
返回了这个info凭证以后,Shiro应该怎么处理呢?
还记得吗,我们在这个Realm的构造器中已经定义了如何去加密明文密码。聪明的Shiro接下来会取出token中的你输入的密码,然后采用构造函数的md5算法,再取出你指定的盐,循环加密md5( passwordFromToken , YourExpectedSalt) 3次。然后会将加密的结果,与getPasswordFromDB(clientUsername)这个值进行对比。如果对比结果一致,即密码正确,那么就说明本次登录成功。否则登录失败。
本次完整的认证流程就结束了。如果你还有点迷糊,那么下面的完整的测试代码应该能够帮助到你哦。o(∩_∩)o
package shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* @author jay.zhou
* @date 2018/12/20
* @time 23:07
*/
public class PasswordRealm extends AuthorizingRealm {
public PasswordRealm() {
//采用md5算法
HashedCredentialsMatcher passwordMatcher = new HashedCredentialsMatcher("md5");
//循环加密3次
passwordMatcher.setHashIterations(3);
//再将这个加密组件注入到我们的Realm中
this.setCredentialsMatcher(passwordMatcher);
}
@Override
public String getName() {
return "PasswordRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String clientUsername = (String) token.getPrincipal();
String yourExpectedSalt = "小大宇";
return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
generateSalt(yourExpectedSalt), getName());
}
private String getPasswordFromDB(String clientUsername) {
//模拟从数据库查出来的密文
return "e36e406088fcbe05c70ec1ab33bffa64";
}
private ByteSource generateSalt(String salt) {
return ByteSource.Util.bytes(salt);
}
//授权,暂时不谈
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
Shiro配置文件 shiro.ini
[main]
myRealm=shiro.PasswordRealm
securityManager.realms=$myRealm
@Test
public void testPasswordRealm() {
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到SecurityManager实例 并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
//验证密码123456是否能够登录成功
UsernamePasswordToken token = new UsernamePasswordToken("anyString", "123456");
try {
//4、登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
//5、身份验证失败
e.printStackTrace();
}
Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
//6、退出
subject.logout();
}
@Test
public void generatePassword() {
String password = "123456";
String csdnName = "小大宇";
Md5Hash md5Hash = new Md5Hash(password, csdnName, 3);
//打印结果:e36e406088fcbe05c70ec1ab33bffa64
System.out.println(md5Hash);
}
junit
junit
4.9
commons-logging
commons-logging
1.1.3
org.apache.shiro
shiro-core
1.2.2
本章节项目源码:点击我下载源码
----------------------------------------------------分割线-------------------------------------------------------
下一篇:第四节 Shiro权限管理
阅读更多:跟着大宇学Shiro目录贴