Spring Security基础 | 02 对密码进行“加密“ —— BCryptPasswordEncoder

Source

对用户密码进行加密

上面的情况中,虽然我们自定义了密码,但这样是无法应用到实际的业务场景的。

在实际业务中,不会通过明文的方式指定密码,这样不安全,而且在配置文件中指定用户名和密码,很麻烦。将用户的密码进行加密,这是最基本的操作。

Spring Security提供了许多加密工具,下面以BCryptPasswordEncoder为例进行介绍。

使用BCryptPasswordEncoder对密码进行编码

注意,本节标题我没有用“加密”,而是用“编码”这个词,至于原因,后文会进行解释。读者可暂且理解我们在用BCryptPasswordEncoder对密码进行加密操作。

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); // 创建BCryptPasswordEncoder对象
String pwd = "123"; // 原始密码
String newPwd = encoder.encode(pwd); // 调用encode方法,对原始密码进行加密
System.out.println(newPwd); // 结果:$2a$10$K3QH5Cbh7veu93FXs7C20OP0AiDeiFT/H3IfEr9BffEE9vfhrq7xm

调用 BCryptPasswordEncoder 对象的encode()方法可以对账号的密码进行加密。加密后保存到数据库中,可以在一定程度上保证用户密码的安全。

BCryptPasswordEncoder采用SHA-256 + 随机盐 + 密钥的方式对密码进行加密。需要注意的是,SHA-256不是加密算法,而是Hash算法,加密算法意味着可以解密,而Hash算法的计算结果是不可逆的,即一般不可能破解。这样,就算数据库信息泄露,骇客想要获取到用户的密码基本上也是不可能的(除非用彩虹表这类工具)。

当用户下次登录时,要对用户的身份进行认证,即判断输入的用户名和密码是否正确。网站后台会从数据库中取出用户名对应的密码,但不会对密码进行解密,因为不可能做到。因此,网站后台采用的方式是将用户登录时输入的密码用BCryptPasswordEncoder进行编码,然后与数据库中对应账号的密码进行比对,从而判断用户输入的密码是否正确。BCryptPasswordEncoder提供了matches方法来进行这类操作:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String pwd = "123";
String newPwd = encoder.encode(pwd);
System.out.println(newPwd); //  结果:$2a$10$K3QH5Cbh7veu93FXs7C20OP0AiDeiFT/H3IfEr9BffEE9vfhrq7xm
System.out.println(encoder.matches(pwd,newPwd)); // 结果:true

matches方法的原理就是将rawPassword对应的参数进行编码,即对该参数调用encode方法,然后与已经经过编码的密码进行比对,进而返回一个布尔结果。

BCryptPasswordEncoder之所以叫Encoder(编码器),而不叫Encryptor(加密器)的原因我觉得在于BCryptPasswordEncoder不是加密工具,因为BCryptPasswordEncoder底层用到了Hash算法,而Hash算法不是加密算法,所以这个过程不能称为加密,但可以称为编码(encode)。


但使用过BCryptPasswordEncoder这个工具的开发者都知道,对于同样的内容(密码),其编码结果每次都不相同,但使用matches()方法得到的结果却能为true,这是为什么呢?

这个和BCryptPasswordEncoder对密码进行编码过程中使用到的随机盐有关,每次调用encode方法,都会生成一个随机盐,而这个随机盐的信息会被存在密码的编码结果中,因此matches方法每次对原始密码和编码后的密码进行比对时,会先取出编码后的密码中的随机盐信息,根据该信息来对原始密码进行编码,最后可以得到与编码后的密码相同的结果,这就是为什么matches()方法得到的结果却能为true。至于随机盐是如何生成的,这个后面有时间再进行研究。

使用Spring框架提供工具进行 md5 加密

下面代码演示了如何使用Spring框架提供的DigestUtils.md5DigestAsHex()方法对密码进行md5加密:

public void md5Test() {
    
      
    // 1 定义密码,盐
    String pwd = "123";
    String salt = "www.baidu.com";
    // 2 创建加密对象,对密码和盐进行不可逆hash加密
    // md5对相同内容加密的结果都是相同的,并且不可逆
    // md5对不同内容加密的结果可能相同,但几率很小
    String encodedPwd = DigestUtils.md5DigestAsHex((pwd + salt).getBytes());
    // 3 输出加密后的密码
    System.out.println(encodedPwd);
    // 第一次运行结果:7de77cdad36b9e73cf82817cd9935f3d
    // 第二次运行结果:7de77cdad36b9e73cf82817cd9935f3d
    System.out.println(encodedPwd.length()); // 32
}

实际项目中md5的盐需要存在数据库中,登录时会基于用户名,将用户信息查询出来,并基于密码和数据库中查出来的盐进行md5加密,再与数据库中存储的密码进行比对,进而判断是否允许登录。