目前市面上流行密码安全管理有如下几种:

  • spring-security 提供的工具类 BCryptPasswordEncoder
  • Shiro 提供的工具类 SimpleHash, 借助 commons-lang3 提供的类 RandomStringUtils 来加盐

注意:本文仅仅比较两种对数据进行加密的方式,不涉及集成到springboot中

当我们对两个字符串进行比较时,通常是: if(“123456”==“654321”),但是这两个库不一样,都提供了各自的专用类,使用专门的方法去比较

spring-security 提供的 BCryptPasswordEncoder

引入坐标:

<!-- BCryptPasswordEncoder 加密-->
  <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
      <version>5.7.3</version>
  </dependency>

在这里我们做了这些操作先对数据 123456 加密 encode 一次,得到加密之后的字符串 hexString
接下来再一次使用数据 123456 ,与刚才得到的字符串 hexString 执行匹配算法,
得到结果: 123456 与字符串 hexStrin 匹配。

@Test
    public void testSpringSecurityEncryptPassword(){
        String password = "123456";
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 第一次
        String hexString = bCryptPasswordEncoder.encode(password);
        System.out.println(hexString);
        // 比较
        boolean isMatch = bCryptPasswordEncoder.matches(password, hexString);
        System.out.println(isMatch);

        // 第二次
        String hexString2 = bCryptPasswordEncoder.encode(password);
        System.out.println(hexString2);
        // 比较
        boolean isMatch2 = bCryptPasswordEncoder.matches(password, hexString);
        System.out.println(isMatch2);
    }

这里我们为什么要这样演示呢?密码不能明文存在数据库中,只能存加密之后的数据。

试想一下,当用户登录时,用户会数据明文数据密码 123456,我们先对此数据数据加密一次,然后将加密之后的字符串 hexString, 存入数据库。
当用户再一次登录时,用户依然会输入明文密码 123456 ,此时数据库中已经有了加密之后的hexString,

再次执行对比,用户输入的 123456执行加密,与存入数据库中的hexString比较。如果匹配上,那么就说明输入的是同一个密码。

这样就实现了数据库存入加密后数据,即使是数据库拥有者也不知道用户的密码是什么。

在演示中,一共执行了2次,可以发现每一次生成的hexString都是不一样的,这样就保证了密码数据的随机安全性。
同时,在两次匹配match中,得到的结果都是true,又保证了输入同样的密码,可以与存入数据库中的字符串相等。

Shiro 提供的 SimpleHash

maven 仓库中搜索Shiro,会发现有很多跟 shiro 相关的包:

  • shiro-core
  • shiro-web
  • shiro-spring
  • shiro-ehcache 缓存
  • shiro-cas 单点登录

我们的目的仅仅是对比 shiro 中的数据密码这一小部分 ,因此只需要引入这个坐标:

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.9.0</version>
</dependency>
  1. 加密:Shiro 提供的类 SimpleHash 可以对密码进行加密。类SimpleHash主要有 3 个参数
  • String algorithmName 算法
  • Object source 被加密的数据
  • Object salt 加密用的盐
  1. 匹配:Shiro 提供的类 SimpleCredentialsMatcher专门用来对密码进行比较是否相等。类 SimpleCredentialsMatcher主要有2参数
  • AuthenticationToken 用户token
  • AuthenticationInfo 认证信息

shiro 比较密码的过程与上面 spring-security比较密码的过程 部分类似。

第一个参数:shiro通过类UsernamePasswordToken允许我们创建第一个参数。
第二个参数: shiro通过接口AuthenticationInfo允许我们创建第二个参数,并且直接提供了它的一个实现类SimpleAuthenticationInfo,便于我们直接使用…

//  对密码 123456 按照 MD5 哈希算法进行了一次加盐(salt)操作
@SpringBootTest
class UserControllerTest {
    @Test
    public void testShiroCore(){

        String username = "ifredom";
        String password = "123456";

        // 加盐(密)
        String salt = RandomStringUtils.randomAlphanumeric(20);// 生成了一个20位长度的随机字符串
        SimpleHash hash = new SimpleHash("md5", password, salt);
        System.out.println(hash.toHex());


        // shiro 提供 SimpleCredentialsMatcher 来比较密码是否相同,该方法需要2参数
        SimpleCredentialsMatcher matcher = new SimpleCredentialsMatcher();
        
        // 第一个参数 AuthenticationToken。这个类又必须提供用户名和密码。
        AuthenticationToken token = new UsernamePasswordToken(username, password);

        // 第二个参数 SimpleAuthenticationInfo。这个类又需要4参数:用户名,密码,字节类型salt,以及一个 realm 名称字符串 .
        /// 注意:这个 realm 由自己实现,shiro 会从javaBean中查找,如果没有找到,那么不会使用 realm内部实现得验证规则。可以称之为更高优先级规则
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                username, //用户名
                password, //密码
                ByteSource.Util.bytes(salt),// salt= username + salt
                "realmName"  //realm name
        );
        
		// 比较
        boolean match = matcher.doCredentialsMatch(token, authenticationInfo);

        if (match) {
            System.out.println("密码相同");
        }else{
            System.out.println("密码不同");
        }
    }
}
hash: 534e3f1e836820220ed938c4337206a8
密码相同

shiro 相比较 spring-security 的比较过程真的麻烦了很多.

commons-lang3 提供的 RandomStringUtils

RandomStringUtils 中提供了很多静态方法:

@Test
public void testCommonLangRandomStringUtils()  {
    /*
      * 从所有字符集中选择字符,产生5位长度的随机字符串,中文环境下是乱码
      * 打印结果:𨱼𠋤တ
      */
    String random1 = RandomStringUtils.random(5);

    /*
      * count 长度
      * letters 生成的字符串是否包括字母字符
      * numbers 生成的字符串是否包含数字字符
      * 打印结果:rPjlaGrKco
      */
    String random2 = RandomStringUtils.random(10, true, false);

    /*
      * 使用指定的字符生成5位长度的随机字符串,第二个参数如果NULL,则使用所有字符集
      * 打印结果:eacdb
      */
    String random3 = RandomStringUtils.random(5, new char[]{'a', 'b', 'c', 'd', 'e', 'f', '1', '2', '3'});

    /*
      * 生成指定长度的字母和数字组成的随机组合字符串
      * 打印结果:RQ7WX
      */
    String random4 = RandomStringUtils.randomAlphanumeric(5);

    /*
      * 创建一个随机字符串,其长度介于包含最小值和最大最大值之间,字符将从拉丁字母(a-z、A-Z)和数字0-9中选择。
      * minLengthInclusive :要生成的字符串的包含最小长度
      * maxLengthExclusive :要生成的字符串的包含最大长度
      * 打印结果:F5VFlEVLSIgtSo4dEBXcXTja
      */
    String random5 = RandomStringUtils.randomAlphanumeric(5, 68);

    /*
      * 生成随机数字字符串
      * 打印结果:25735
      */
    String random6 = RandomStringUtils.randomNumeric(5);

    /*
      * 创建一个随机字符串,其长度介于包含最小值和最大最大值之间,将从数字字符集中选择字符.
      * minLengthInclusive, 要生成的字符串的包含最小长度
      * maxLengthExclusive 要生成的字符串的包含最大长度
      * 打印结果:710920665944410618
      */
    String random7 = RandomStringUtils.randomNumeric(15, 20);

    /*
      * 生成随机[a-z,A-Z]字符串,包含大小写
      * 打印结果:HzVus
      */
    String random8 = RandomStringUtils.randomAlphabetic(5);

    /*
      * 创建一个随机字符串,其长度介于包含最小值和最大最大值之间,,字符将从拉丁字母(a-z、A-Z的选择)。
      * minLengthInclusive :要生成的字符串的最小长度
      * maxLengthExclusive :要生成的字符串的最大长度
      * 打印结果:MIwdfReXxEoi
      */
    String random9 = RandomStringUtils.randomAlphabetic(2, 15);

    /*
      * 生成ASCII值从32到126组成的随机字符串
      * 打印结果:}t;r
      */
    String random10 = RandomStringUtils.randomAscii(4);

    /*
      * 创建一个随机字符串,其长度介于包含最小值和最大最大值之间,字符将从ASCII值介于32到126之间的字符集中选择(包括)
      * minLengthInclusive :要生成的字符串的包含最小长度
      * maxLengthExclusive :要生成的字符串的包含最大长度
      * 打印结果:ye{.jAaF`p#mjf>Apo
      */
    String random11 = RandomStringUtils.randomAscii(15, 30);
}