在之前的登录案例中,如果我们手动将密码加密,那么保存在数据库的密码就是加密后的密码,这时我们在 Realm
中从数据库获取的密码也就是加密后的密码,返回给 Shiro 进行比对的时候 Shiro 如果还是使用默认页面输入的原始密码与数据库中加密后的密码比对,那么肯定是不可行的。通过之前登录功能的实现我们已经知道 Shiro 是通过 org.apache.shiro.authc.credential.CredentialsMatcher
类实例的 doCredentialsMatch
方法来完成页面传入的密码和实际的密码的比对。显然,该实例的 doCredentialsMatch
方法已经满足不了我们的要求。通过查看源码我们已经知道,org.apache.shiro.authc.credential.CredentialsMatcher
类实例实际上是我们自定义认证类父类中的一个属性,所以我们可以通过给该属性注入一个满足我们要求的 org.apache.shiro.authc.credential.CredentialsMatcher
类的子类实例来重写 doCredentialsMatch
。当然我们可以手动定义子类继承它对原来的 doCredentialsMatch
方法进行重写,但其实 Shiro 已经给我们提供了一些加密类,我们只需要配置一下注入就行。如下:
1、在上面登录示例的基础上修改 Spring 配置文件,给 LoginRealm
父类属性 credentialsMatcher
的注入我们需要的 CredentialsMatcher
实例,我这里使用 MD5 加密:
<bean id="jdbcRealm" class="com.zze.shiro.realms.LoginRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
2、修改 LoginRealm
为如下:
// com.zze.shiro.realms.LoginRealm
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
public class LoginRealm extends AuthenticatingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// authenticationToken 实际就是 Controller 中通过 Subject.Login 方法传入的 token,保存了前台输入的密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
// 获取页面传入的用户名
String username = usernamePasswordToken.getUsername();
// 根据用户名从数据库获取密码,假如获取到的是 123456 加密后的密码
Object credentials = "123456"; // 要加密的对象
String hashAlgorithmName = "MD5"; // 加密方式
ByteSource salt = ByteSource.Util.bytes(username); // 以用户名当做盐值
int hashIterations = 1024; // 加密次数,与 Spring 中配置一致
credentials = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
// 假如 unknown 用户名不存在
if ("unknown".equals(username)) {
throw new UnknownAccountException("用户名不存在");
}
// 假如 monster 用户名已经被锁定
if ("monster".equals(username)) {
throw new LockedAccountException("用户已被锁定");
}
// 根据用户情况,构建 AuthenticationInfo 对象返回,通常使用的实现类为 SimpleAuthenticationInfo
// a. principal : 认证的实体信息,可以是 username,也可以是用户对应的实体对象
Object principal = username;
// b. credentials : 密码
// c. realmName : 当前 realm 对象的 name,调用父类的 getName() 方法获得
String realmName = getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
return simpleAuthenticationInfo; // 返回认证信息,交给 Shiro 完成比对
}
}
源码分析
做完上述操作后,此时再启动项目,比对密码的方法就是下面这个方法了:
// org.apache.shiro.authc.credential.HashedCredentialsMatcher#doCredentialsMatch
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// <1>
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
通过 <1>
处可以看到,现在获取的 tokenHashedCredentials
即页面输入的密码是通过 hashProvidedCredentials(token, info)
加密后的密码,查看该方法:
// org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo)
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
} else {
//retain 1.0 backwards compatibility:
if (isHashSalted()) {
salt = getSalt(token);
}
}
// <2>
return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}
继续看到 <2>
处的 hashProvidedCredentials(token.getCredentials(), salt, getHashIterations())
方法:
// org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(java.lang.Object, java.lang.Object, int)
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
String hashAlgorithmName = assertHashAlgorithmName();
// <3>
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}
可以看到,加密工作其实是通过 <3>
处的 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations)
来完成的。
// hashAlgorithmName : 加密方式
// credentials : 被加密的对象
// salt : 盐值
// hashIterations :加密次数
new org.apache.shiro.crypto.hash.SimpleHash(String hashAlgorithmName, Object credentials, Object salt, int hashIterations)
评论区