循序渐进学spring security 第八篇,如何配置密码加密?是否支持多种加密方案?

回顾

前面我们介绍了spring security的登录验证,涉及到密码的都是明文存储,明文匹配的,这在企业项目开发中是不允许的,要是不小心被客户知道了,会投诉企业,指不定会破产,好了,在阅读本文之前,建议先看我之前的文章,方便更好的理解!

  1. 面试不要在说不熟悉spring security了,一个demo让你使劲忽悠面试官
  2. 循序渐进学习spring security 第二篇,如何修改默认用户?
  3. 循序渐进学习spring security 第三篇,如何自定义登录页面?登录回调?
  4. 循序渐进雪spring security 第四篇,登录流程是怎样的?登录用户信息保存在哪里?
  5. 循序渐进学习spring security 第五篇,如何处理重定向和服务器跳转?登录如何返回JSON串?
  6. 循序渐进学spring security第六篇,手把手教你如何从数据库读取用户进行登录验证,mybatis集成
  7. 循序渐进学spring security 第七篇,如何基于用户表和权限表配置权限?越学越简单了

密码明文会带来什么问题?

2011 年 12 月,有人在网络上公开了600 万个 CSDN 用户资料的数据库,数据全部为明文储存,包含用户名、密码以及注册邮箱。事件发生后 CSDN 在微博、官方网站等渠道发出了声明,解释说此数据库系 2009 年备份所用,因不明原因泄露,已经向警方报案,后又在官网发出了公开道歉信。在接下来的十多天里,金山、网易、京东、当当、新浪等多家公司被卷入到这次事件中。整个事件中最触目惊心的莫过于 CSDN 把用户密码明文存储,由于很多用户是多个网站共用一个密码,因此一个网站密码泄露就会造成很大的安全隐患。由于有了这么多前车之鉴,我们现在做系统时,密码都要加密处理。

2018年,我在一家专门做户外平台的企业工作,该企业主要是做户外活动平台,用户量有300万+,进入公司一段时间后,发现了一个重要秘密,用户的密码竟然是明文存储在数据库中的!这是多少危险的事情啊,万一被不怀好意的人把密码泄漏出去,指不定会做出来什么坏事!可能会让客户倾家荡产,客户倾家荡产,这家公司还能平安无事吗?

而作为开发工程师,在企业项目开发中,也许你不是项目的负责人,但是确实执行人,如果出现密码明文泄漏了,企业被投诉,会不找你来背锅吗?到时只能吃哑巴亏了

如何加密?

密码加密一般会用到散列函数,又称散列算法、哈希函数,这是一种从任何数据中创建数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,重新创建一个散列值。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。我们常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)。

仅仅使用散列函数还不够,为了增加密码的安全性,一般在密码加密过程中还需要加盐,所谓的盐可以是一个随机数也可以是用户名,加盐之后,即使密码明文相同的用户生成的密码密文也不相同,这可以极大的提高密码的安全性。但是传统的加盐方式需要在数据库中有专门的字段来记录盐值,这个字段可能是用户名字段(因为用户名唯一),也可能是一个专门记录盐值的字段,这样的配置比较繁琐。

Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。

不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就自带了盐,处理起来非常方便。

而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的实现类。

PasswordEncoder 加密接口

PasswordEncoder 这个接口中就定义了三个方法

publicinterface PasswordEncoder {
    
    
	String encode(CharSequence rawPassword);
	boolean matches(CharSequence rawPassword, String encodedPassword);
	default boolean upgradeEncoding(String encodedPassword) {
    
    
		return false;
	}
}
  • encode 方法用来对明文密码进行加密,返回加密之后的密文。
  • matches 方法是一个密码校对方法,将用户传来的明文密码和数据库中保存的密文密码作为参数,传入到这个方法中去,根据返回的 Boolean 值判断用户密码是否正确。
  • upgradeEncoding 是否还要进行再次加密,这个一般来说就不用了。

如何配置?

通过前面的文章介绍了密码明文的配置

   @Bean
    PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }
``

那如果配置加密,自然而然简单了

```java
    @Bean
    PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

就这样,就完成了密码按加密来校验了,这里有个前提,密码需要按加密后存储,否则就无法匹配成功
在这里插入图片描述
重新使用String encode = new BCryptPasswordEncoder().encode(“123456”); 对密码加密后,将加密串替代数据库的明文秘密,再来登录就OK了

在这里插入图片描述
在这里插入图片描述

加密的密码在登录的时候是怎么校验的?

Spring Security 中,如果使用用户名/密码的方式登录,密码是在 DaoAuthenticationProvider 中进行校验的,大家可以参考《循序渐进雪spring security 第四篇,登录流程是怎样的?登录用户信息保存在哪里?

在这里插入图片描述
可以看到,密码校验就是通过 passwordEncoder.matches 方法来完成的。

那么 DaoAuthenticationProvider 中的 passwordEncoder 从何而来呢?是不是就是在 SecurityConfig 中配置的那个 Bean 呢?

我们来看下 DaoAuthenticationProvider 中关于 passwordEncoder 的定义,如下:
在这里插入图片描述
可以看到DaoAuthenticationProvider 在构造方法中就已经初始化了一个默认的passwordEncoder,如果我们没有配置passwordEncoder,就会用默认的,如果我们配置了passwordEncoder的bean,就用配置的 。

默认的加密是什么?

如果我们不进行任何配置,默认的 PasswordEncoder 也会被提供,那么默认的 PasswordEncoder 是什么呢?

public DaoAuthenticationProvider() {
    
    
 setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
public class PasswordEncoderFactories {
    
    
 public static PasswordEncoder createDelegatingPasswordEncoder() {
    
    
  String encodingId = "bcrypt";
  Map<String, PasswordEncoder> encoders = new HashMap<>();
  encoders.put(encodingId, new BCryptPasswordEncoder());
  encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
  encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
  encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
  encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
  encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
  encoders.put("scrypt", new SCryptPasswordEncoder());
  encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
  encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
  encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
  encoders.put("argon2", new Argon2PasswordEncoder());

  return new DelegatingPasswordEncoder(encodingId, encoders);
 }

 private PasswordEncoderFactories() {
    
    }
}

可以看到:

  • 在 PasswordEncoderFactories 中,首先构建了一个 encoders,然后给所有的编码方式都取了一个名字,再把名字做 key,编码方式做 value,统统存入 encoders 中。
  • 最后返回了一个 DelegatingPasswordEncoder 实例,同时传入默认的 encodingId 就是 bcrypt,以及 encoders 实例,DelegatingPasswordEncoder 看名字应该是一个代理对象。

DelegatingPasswordEncoder 有什么作用?
DelegatingPasswordEncoder 也是实现了 PasswordEncoder 接口,所以它里边的核心方法也是两个:encode 方法用来对密码进行编码,matches 方法用来校验密码。
如果我们想同时使用多个密码加密方案,就使用 DelegatingPasswordEncoder 就可以了,而 DelegatingPasswordEncoder 默认还不用配置

DaoAuthenticationProvider 是怎么初始化的?怎么设置用户配置的PasswordEncoder?

再来看看 DaoAuthenticationProvider 是怎么初始化的。

DaoAuthenticationProvider 的初始化是在 InitializeUserDetailsManagerConfigurer#configure 方法中完成的,我们一起来看下该方法的定义:
在这里插入图片描述
从这段代码中我们可以看到:

  • 首先去调用 getBeanOrNull 方法获取一个 PasswordEncoder 实例,getBeanOrNull 方法实际上就是去 Spring 容器中查找对象。
  • 接下来直接 new 一个 DaoAuthenticationProvider 对象,大家知道,在 new 的过程中,DaoAuthenticationProvider 中默认的 PasswordEncoder 已经被创建出来了。
  • 如果一开始从 Spring 容器中获取到了 PasswordEncoder 实例,则将之赋值给 DaoAuthenticationProvider 实例,否则就是用 DaoAuthenticationProvider 自己默认创建的 PasswordEncoder。

至此,就真相大白了,我们配置的 PasswordEncoder 实例确实用上了。

总结:本文介绍了加密的重要性,为什么加密,如何加密的相关配置,还介绍了在默认不配置加密的情况下就已经使用加密的方式进行校验,而且是支持多种加密方案的,这一步的操作实验,留给读者自己去尝试吧

猜你喜欢

转载自blog.csdn.net/huangxuanheng/article/details/119220876
今日推荐