Shiro学习——密码的加密解密

Posted sadoshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro学习——密码的加密解密相关的知识,希望对你有一定的参考价值。

前言

前面几篇文章的密码都是以明文形式存储。在真实项目中当然不可能明文存储密码,密码一定是以加密的形式存储的。前面我们可以看到当我们给出帐号和密码后,shiro就会去查找ini配置文件或者数据库对应字段来匹配账号密码。那如果我们把存储的密码加密,shiro又如何根据我们提交的明文密码与存储的加密密码匹配呢?

Shiro加密与匹配的原理

这里不准备展示源码。相信很多读者看文章时面对大量源码也看得一头雾水,并不能总结出什么来,所以这里大致讲讲思路,有兴趣的读者可以自行跟踪源码。这里以读取ini文件为例:

1、当我们使用subject.login(token)这个命令时,会调用IniRealm的父类SimpleAccountRealm的doGetAuthenticationInfo获取存储的认证信息。PS:如果我们自定义realm的话,如何获取认证通常也是重写这个方法。

2、通过AuthenticatingRealm的assertCredentialsMatch方法对比我们提交的帐号密码token与上一步获取的认证信息是否匹配。如果匹配就返回认证信息,否则就抛出异常。

3、第2步匹配的方法调用CredentialsMatcher接口的doCredentialsMatch方法。例如默认的匹配类为SimpleCredentialsMatcher,它的doCredentialsMatch非常简单,就是比较提交的密码与第1步获取的认证密码是否一致。这过程没做任何加密解密处理。所以对于加密或者解密操作,最主要的实现在doCredentialsMatch方法上进行。

shiro本身提供了一些加密解密的类和方法,例如PasswordMatcher类,当然也可以自定义,只需配置到securityManager中即可。

配置加密解密类

匹配类

shiro默认的匹配类是SimpleCredentialsMatcher。读者可以参考下其源码,实现非常简单。shiro提供的匹配接口是CredentialsMatcher,只要实现doCredentialsMatch方法即可:

public interface CredentialsMatcher 
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);

加密与验证服务

shiro提供了加密与验证服务的接口PasswordService接口:

public interface PasswordService 
    String encryptPassword(Object plaintextPassword) throws IllegalArgumentException;
    boolean passwordsMatch(Object submittedPlaintext, String encrypted);

第一个方式用于加密,第二个方法用于进行对密码进行匹配。shiro自身提供的加密解密类都实现了这个接口。通常这个接口是配合CredentialsMatcher使用的。一般CredentialsMatcher的doCredentialsMatch方法对提交的帐号密码与存储的帐号密码信息进行一些处理,后面再调用PasswordService的passwordsMatch方法进行匹配。

为什么有CredentialsMatcher还要设计PasswordService呢?

我的理解,只用CredentialsMatcher也可以。默认匹配类SimpleCredentialsMatcher就没有使用PasswordService。CredentialsMatcher接口doCredentialsMatch传入的参数是AuthenticationToken(用户登录提交的帐号密码信息)和AuthenticationInfo(系统存储的帐号密码信息),我们可以在doCredentialsMatch中提取并组合后放到PasswordService的passwordsMatch中处理。把PasswordService提出来可以更方便替换不同的加密算法。

代码示例

现在做一个简单的例子,展示一下PasswordService和CredentialsMatcher怎么使用。shiro默认的CredentialsMatcher是PasswordMatcher,默认的PasswordService是DefaultPasswordService。即使我们不设置也行,但例子中还是希望手动设置,让读者更好理解。

首先我们先得到一个加密的密码:

public class PassWordCrypto 
	
	public static String encriptPassword(String password) 
		PasswordService service = new DefaultPasswordService();
		return service.encryptPassword(password);
	
	
	public static void main(String[] args) 	
		System.out.println(encriptPassword("123"));
	

执行上面那段代码,得到密码“123”加密结果为$shiro1$SHA-256$500000$3mRjauseIWB330SU++9msA==$eHTXKKktQduiDi6Kut8HzwBJHeiwx7pXDemieEhgDkE=

 

我们本次以ini位置文件为例子,创建password.ini,并把刚才得到的加密密码填入:

[main]
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
passwordService=org.apache.shiro.authc.credential.DefaultPasswordService
passwordMatcher.passwordService=$passwordService
iniRealm.credentialsMatcher=$passwordMatcher
securityManager.realms=$iniRealm
[users]
zhang=$shiro1$SHA-256$500000$3mRjauseIWB330SU++9msA==$eHTXKKktQduiDi6Kut8HzwBJHeiwx7pXDemieEhgDkE=,role1
li=123
[roles]
role1=user:create,update
role2=user:create,delete

上面[main]中把DefaultPasswordService设置到PasswordMatcher中,再把PasswordMatcher设置到iniRealm中。这里的iniRealm是默认创建出来的,如果我们自己新建一个IniRealm设置进去的话反而会报错。

接着写测试代码:

package com.sadoshi.shiro.crypto;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.env.BasicIniEnvironment;
import org.apache.shiro.env.Environment;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

public class PasswordServiceTest 
	
	private Subject subject;
	
	@Before
	public void init() 
		Environment env = new BasicIniEnvironment("classpath:password.ini");
		org.apache.shiro.mgt.SecurityManager securityManager = env.getSecurityManager();
		SecurityUtils.setSecurityManager(securityManager);
		subject = SecurityUtils.getSubject();
	

	@Test
	public void hashCrypto() 
		UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
		try 
			subject.login(token);
			System.out.println("ok");
		 catch (AuthenticationException e) 
			e.printStackTrace();
		
		subject.logout();
	

如果密码对不上,就会抛出异常。我们这里执行结果正确。

小结

本文只是初步展示一下CredentialsMatcher和PasswordService是怎么使用的。后面的文章还会展示其他加密方式。

以上是关于Shiro学习——密码的加密解密的主要内容,如果未能解决你的问题,请参考以下文章

Shiro学习——MD5加密与盐值

Shiro学习——MD5加密与盐值

shiro学习三

第五章:shiro密码加密

shiro 忘记加密后的密码怎么办?

Shiro安全框架学习03 - 编码/加密