跳到主要内容

10、Shiro 实战:Shiro密码加密匹配

下面我们来说一下在Shiro中对密码的加密。我们知道线上系统的数据库中存储的密码不应该是明文,而是密码加密后的字符串,并且要求加密算法是不可逆的。著名的加密算法有MD5、SHA1等。其中MD5是目前比较可靠的不可逆的加密方式。

我们如何利用Shiro实现用户登录密码的MD5加密呢?这就需要让Shiro的自定义Realm去使用带有加密机制的CredentialsMatcher密码匹配类。如何做到呢?这就需要替换当前Realm的credentialsMatcher属性。在上一篇总结中,我们看到了自定义的ShiroRealm(AuthenticatingRealm为父类)执行的assertCredentialsMatch方法调用的是CredentialsMatcher类来进行密码比较的,而这个CredentialsMatcher类有很多实现类,例如MD5CredentialsMatcher,而这个类现在已经过时了,官方推荐使用HashedCredentialsMatcher来进行加密密码的匹配工作。所以下面我们在自定义Realm中直接使用HashedCredentialsMatcher对象,并设置加密算法即可。

首先更改Shiro的配置文件applicationContext.xml,将自定义的Realm中的CredentialsMatcher
的实现类注入为HashedCredentialsMatcher:

<bean id="shiroRealm" class="com.test.shiro.realms.ShiroRealm">
	<property name="credentialsMatcher">
	    <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
	        <property name="hashAlgorithmName" value="MD5"></property>
                <property name="hashIterations" value="1024"></property>
	    </bean>
	</property>
</bean>

注入HashedCredentialsMatcher类型实现类,并将其中的加密算法名称属性hashAlgorithmName设置为“MD5”,加密次数设置为1024次。设置完上面的密码匹配类之后,就可以将用户的登录密码自动加密为MD5。我们为之前的测试系统的assertCredentialsMatch方法源码打一个断点:

 

当用户输入账号密码后,可以在assertCredentialsMatch方法中看到CredentialsMatch的类型为HashedCredentialsMatcher:

 

而在HashedCredentialsMatcher的doCredentialsMatch方法,可以看到密码已经被加密为MD5:

 

那么HashedCredentialsMatcher怎么给密码进行加密的?观察HashedCredentialsMatcher的

中doCredentialsMatch方法:

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
	Object tokenHashedCredentials = hashProvidedCredentials(token, info);
	Object accountCredentials = getCredentials(info);
	return equals(tokenHashedCredentials, accountCredentials);
}

可以发现其中有一个hashProvideCredentials方法,在该方法中,给取出了盐值的操作 判断info的类型是否是 SaltedAuthenticationInfo :

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
        Object salt = null;
        if (info instanceof SaltedAuthenticationInfo) {
            salt = ((SaltedAuthenticationInfo)info).getCredentialsSalt();
        } else if (this.isHashSalted()) {
            salt = this.getSalt(token);
        }

        return this.hashProvidedCredentials(token.getCredentials(), salt, this.getHashIterations());
    }

然后又返回了一个传入新参数hashIterations(加密次数)的hashProvideCredentials方法,在该方法中,提供了密码加密的逻辑:

protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = assertHashAlgorithmName();
    return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}

可以看到返回了一个SimpleHash对象(org.apache.shiro.crypto.hash.SimpleHash),该构造方法返回的就是一个加密后的Hash值。在该构造方法中有四个参数,其中hashAlgorithmName指定的是加密方式,即是之前我们注入在XML的参数(value为MD5),credentials为加密前的密码。salt代表盐值,hashIterations为加密次数(加密次数越多越安全)。

我们做一个测试,来实验SimpleHash的加密方法:

public static void main(String[] args) {
    String hashAlgorithmName = "MD5";//加密方式
    Object crdentials = "123456";//密码原值
    Object salt = null;//盐值
    int hashIterations = 1024;//加密1024次
    Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations);
    System.out.println(result);
}

结果:

 

可以看到,MD5加密成功、说明在Shiro中,是使用org.apache.shiro.crypto.hash.SimpleHash类的构造方法进行加密并返回加密结果的。

提示:想要进一步学习如何使用Java对一个字符串进行MD5加密,详见下面博文:

《Java实现四大加密算法》http://blog.csdn.net/acmman/article/details/51830744

有关密码加盐的问题请看下一篇总结。