上一篇文章讲了shiro如何配置加密服务。这篇文章我们用md5加密算法作为例子。md5算法是常见的加密算法,另外为了提高安全性,通常还会加上盐值。这些如何进行配置呢,在本文进行展示。
像MD5这类加密,对于同一个密码,加密之后的密码都是一样的。如果我们初始化系统,把所有用户设置密码为123456,那经过md5加密之后,所有密文也都是相同的。那盗用者只要破解这个密文,那所有密文相同的帐号也会被盗用。如何避免这种情况呢?加盐。加盐的操作,就是明文加密过程中,把盐(其实就是一小截字符串,例如abc)和明文拼接到一起加密。因为每个帐号的盐值不同,同样明文,加密出来的密文都不一样,这样帐号安全性就大大提高了。
一开始我是准备把用户名帐号弄在ini文件上的形式,但是折腾了好久都不行。查看了源码,因为一是IniRealm读取ini文件的帐号密码,就没有读取盐值的功能,二是使用的SimpleAccount类存储认证信息也不包含盐值属性。所以如果读者想使用ini文件进行帐号密码管理的话,必须重写这个类的一些方法才行。由于比较麻烦,我就放弃这条路线了。
我选择使用数据库配置帐号密码的形式,这样默认的JdbcRealm是支持包含盐值的密文存储方式。看系列第一篇文章《Shiro学习(一)——Shiro配置与快速开始》,如下图所示,默认users表的password_salt就是存储盐值用的。
接着我们编写生成加密的密文和盐值的方法,这里我们传入123作为密码,以abcd作为盐值,以md5作为加密算法,进行10次加密迭代:
public void encryptMD5(String password) { System.out.println("加密后的盐值:"+Base64.encodeToString("abcd".getBytes())); SimpleHash hash = new SimpleHash("md5", password, "abcd", 10); System.out.println("加密后的密码:"+hash.toString()); }
执行这段代码,输出结果:
由于JdbcRealm默认会对盐值作Base64解密,我们应该把加密后的盐值写入数据库中,如下图:
这样准备工作就完成了
下一步,我们通过数据库配置方式,测试认证是否生效。编写如下代码:
public static void main(String[] args) throws Exception { DefaultSecurityManager securityManager = new DefaultSecurityManager(); JdbcRealm realm = new JdbcRealm(); Class<DruidDataSource> clazz = DruidDataSource.class; DruidDataSource dataSource = clazz.newInstance(); dataSource.setUsername("root"); dataSource.setPassword(""); dataSource.setUrl("jdbc:mysql://localhost:3306/shiro"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); realm.setDataSource(dataSource); realm.setPermissionsLookupEnabled(true); HashedCredentialsMatcher cm = new HashedCredentialsMatcher(); cm.setHashAlgorithmName("md5"); cm.setHashIterations(10); realm.setCredentialsMatcher(cm); realm.setSaltStyle(SaltStyle.COLUMN); securityManager.setRealm(realm); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { subject.login(token); System.out.println("ok"); } catch (AuthenticationException e) { e.printStackTrace(); } subject.logout(); }
2-11行是配置数据库连接的代码,这个我们前面的文章也有讲,就不细述了。13-17行是匹配密码的关键。上一篇文章《Shiro学习(五)——密码的加密解密》我们讲过,密码匹配的关键点之一是CredentialsMatcher接口的实现类。因为这次使用md5加密,所以我们就用HashedCredentialsMatcher类来进行配陪。14行设置加密算法为md5,15行设置迭代次数为10次,这些配置与我们之前加密的方式要一致。16行把HashedCredentialsMatcher配置到jdbcRealm中,当需要匹配帐号密码时,jdbcRealm就会调用HashedCredentialsMatcher了。17行设置加盐方式为SaltStyle.COLUMN。默认情况是SaltStyle.NO_SALT不加盐。
执行代码,如果没其他异常的话,应该会输出ok字样。如果报下图的错误,那就是帐号密码不匹配。这种情况需要检查帐号密码是否配置正确,盐值是否有加密,是否设置了盐值加密方式SaltStyle.COLUMN。
如果还是找不到,那就要断点到org.apache.shiro.authc.credential.HashedCredentialsMatcher的doCredentialsMatch方法里,看看传入的token(用户提交的信息)与info(数据库获取的信息)是否有问题,再寻找问题出在哪。
这部分内容并不复杂,但是我在使用ini文件配置时卡了很久,另外在盐值也要经过base64加密的问题上也卡了很久。后来都需要通过跟踪源码调试才明白。网上找的文章,绝大多数都是抄来抄去,基本都是自定义一个realm,还把帐号密码盐值写在里面。如果直接搬来改造,还是挺麻烦的。这么简单的业务功能shiro已经提供,我们没必要什么都去扩展,去自定义。