【深入浅出 Spring Security(六)】一文搞懂密码的加密和比对

Spring Security 中的密码加密

  • 一、PasswordEncoder 详解
    • 常见的实现类(了解)
    • DelegatingPasswordEncoder
      • 源码分析
      • DelegatingPasswordEncoder 在哪实例化的?
  • 二、自定义加密
    • 自定义方式一:使用{id}的形式
    • 自定义方式二:向Spring容器中注入PasswordEncoder对象
  • 三、总结

在【深入浅出Spring Security(三)】默认登录认证的实现原理 中小编阐述了在登录认证时,默认情况下,是在 DaoAuthenticationProvider 中的 additionalAuthenticationChecks 方法中进行密码认证的,但没有具体说怎么认证的。本文就具体说说密码的加密和比对吧。

一、PasswordEncoder 详解

在 Spring Security 中,PasswordEncoder 是一个接口,具体源码如下。

public interface PasswordEncoder {// 该方法对明文密码进行加密String encode(CharSequence rawPassword);// 该方法用来进行密码比对,明文和密文比对boolean matches(CharSequence rawPassword, String encodedPassword);// 该方法用来判断当前密码是否需要升级,可以看见这个方法是默认的// 默认返回值是 falsedefault boolean upgradeEncoding(String encodedPassword) {return false;}
}

从源码中可以发现,Spring Security 是通过 PasswordEncoder 去实现密码的加密和比对的。俩必要的抽象方法:

  • encode:对明文密码进行加密;
  • matches:进行密码比对。

常见的实现类(了解)

  • NoOpPasswordEncoder
    • 没有加密,就是明文
  • BCryptPasswordEncoder
    • 使用 bcrypt 算法对密码进行了加密,为了提高密码的安全性,bcrypt 算法故意降低运行速度,以增强密码破解的难度。同时 BCryptPasswordEncoder 为自己加盐,开发者不需要额外维护一个 盐 字段,使用 BCryptPasswordEncoder 加密后的字符串就已经带盐了,即使使用铭文每次生成的加密字符串的不相同。
  • Argon2PasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder

咱下面就只拿 BCryptPasswordEncoder 进行事例说明,所以就解释上面俩吧,不多说了。

DelegatingPasswordEncoder

DelegatingPassword 是 PasswordEncoder 的一个实现类,也是自 Spring Security 5 之后默认使用的加密方案。

从其类名上看,我们可以初步判定它内部用了委派(Delegate)设计模式,它主要是根据实际密文委派实际的密码比对方案。

委派模式(Delegate Pattern)又叫委托模式,是一种面向对象的设计模式,允许对象组合实现与 继承相同的代码重用。它的基本作用就是负责任务的调用和分配任务,是一种特殊的静态代理,可以理 解为全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式,不属于 GOF 23 种设计模式中。
————————————————
通俗地说就是:让一委派对象去判断用哪个对象去处理这个业务,就是让委派对象去做抉泽。通常类名中带有Delegate或Dispatcher的就用了这种设计模式。

很多人奇怪为什么早期使用的 NoOpPasswordEncoder 不直接改为 BCryptPasswordEncoder,而是选择了 DelegatingPasswordEncoder。下面是官方文档中给出如果那样改变会有什么麻烦:

  • 有很多应用程序使用旧的密码编码不容易进行迁移;
  • 密码存储的最佳实践就被更改了;
  • 而 Spring Security 作为一个框架而言,不能这么轻易地带破坏性的更改。

而换成 DelegatingPasswordEncoder 则解决了所有问题:

  • 确保使用的密码编码可以进行规范的正确的密码存储;
  • 允许以现代和遗留格式验证密码;
  • 允许将来升级编码;

源码分析

先了解其属性成员

public class DelegatingPasswordEncoder implements PasswordEncoder {// 默认包裹id的前缀private static final String DEFAULT_ID_PREFIX = "{";//默认包裹id的后缀private static final String DEFAULT_ID_SUFFIX = "}";// 实际包裹id的前缀private final String idPrefix;// 实际包裹 id 的后缀private final String idSuffix;// 实际加密的idprivate final String idForEncode;// 实际加密的方案对象private final PasswordEncoder passwordEncoderForEncode;// 用来委托时候的方案映射private final Map<String, PasswordEncoder> idToPasswordEncoder;// 密码对比方案对象private PasswordEncoder defaultPasswordEncoderForMatches;}

根据调试可以发现,默认构造后的各个属性初始化结果如下:

在这里插入图片描述

根据上面的调试结果,DelegatingPasswordEncoder 根据 {id} 委派方案时可以有如下选择:
在这里插入图片描述
DelegatingPasswordEncoder 的加密实现,实际就是用 BCryptPasswordEncoder 去进行加密后的结果。

    public String encode(CharSequence rawPassword) {return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword);}

咱再看看它内部比对密码的实现,实际这里就进行了委派,委派正确的方案,然后再让委派后的对象进行比对。下面对源码进行了注释,可以看看。

    public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {// rawPassword 是原密码,就咱用户输入的// prefixEncodePassword 可以理解为是保存在数据库中的密码if (rawPassword == null && prefixEncodedPassword == null) {return true;} else {// 去获取{id}中的idString id = this.extractId(prefixEncodedPassword);// 根据 id 获取实际方案PasswordEncoderPasswordEncoder delegate = (PasswordEncoder)this.idToPasswordEncoder.get(id);if (delegate == null) {return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);} else {// 然后如果委派成功就进行比对String encodedPassword = this.extractEncodedPassword(prefixEncodedPassword);return delegate.matches(rawPassword, encodedPassword);}}}

DelegatingPasswordEncoder 在哪实例化的?

实际上在 AuthenticationConfiguration 中配置 AuthenticationManagerBuilder ,将其载入到了 AuthenticationBuilder 实例化对象中了。

在这里插入图片描述从上面并未看出它在哪实例化的,但可以看见创建了一个 LazyPasswordEncoder,它是一个静态内部类,里面有个getPasswordEncoder方法,里面用了单例,私有化的方法,LazyPasswordEncoder 里的encode、mathches等方法都是依赖这个单例对象去进行的。可以看见它是先去调用 getBeanOrNull 这个方法去获取PasswordEncoder对象(从Spring容器中),如果不存在,就去构造 DelegatingPasswordEncoder。在它里面通过 PasswordEncoderFactories 工厂构造的 DelegatingPasswordEncoder。

在这里插入图片描述
在这里插入图片描述
下面是 DefaultPasswordEncoderAuthenticationManagerBuilder 内部类(继承了AuthenticationManagerBuilder)重写的三个 UserDetailsService 配置的方法。passwordEncoder 方法实际是给 DaoAuthentionProvider 中的 赋值…了解了解就好了

在这里插入图片描述其实在实例化 DaoAuthenticaitonProvider 的时候,也会对 passwordEncoder 进行初始化,同样是通过 PasswordEncoderFactories 进行构造的 DelegatingPasswordEncoder。根据上面重写的方法可以发现最后还是会换成 defaultPasswordEncoder,也就是 LazyPasswordEncoder 实例。

在这里插入图片描述综上所述:实际注入形式有两种,一种是采用默认的,从PasswordEncoderFactories 中实例化对象;另一种是向Spring容器中注入自己想使用的PasswordEncoder。

二、自定义加密

通过上面对Spring Security 5 之后默认使用的 PasswordEncoder(DelegatingPasswordEncoder)的源码分析,相信下面对自定义加密的两种方式会很轻松的掌握并理解。

自定义方式一:使用{id}的形式

从上面的源码分析我们可以知道,默认的DelegatingPasswordEncoder 会去找密码前面的 id ,去委派方案进行比对密码,所以我们的密码在前面加上想要匹配的方案 id ,就可以了。这种方式呢,比较灵活,可以根据自己想要的比对方式去配 id 即可,密码形式比较灵活。缺点就是对应方案 id 咱也不好记,所以记住有 PasswordEncoderFactories 这么一个类小编自认为是很有必要的。

方案 id ,我们在PasswordEncoderFactories中查找到,如下:

在这里插入图片描述测试案例:

写个测试案例,看看 123 用 BCrypt 加密后的结果。

    @Testpublic void test(){// 输出:$2a$10$0BBFHiDx9jmix3FldDvFYexYGrOascxKcDagaG1wW7LpnPeQIjBcaSystem.out.println(new BCryptPasswordEncoder().encode("123"));}

然后在配置 UserDetailsService 中进行配置一下。

    @Beanpublic InMemoryUserDetailsManager inMemoryUserDetailsManager(){return new InMemoryUserDetailsManager(User.withUsername("root").password("{bcrypt}$2a$10$0BBFHiDx9jmix3FldDvFYexYGrOascxKcDagaG1wW7LpnPeQIjBca").roles("admin").build());}

测试结果
请添加图片描述

自定义方式二:向Spring容器中注入PasswordEncoder对象

上面有对 LazyPasswordEncoder 中的 getPasswordEncoder 方法进行源码分析,说实际是先从Spring容器中找,没有的话再通过PasswordEncoderFactories进行构建。所以我们直接创建一个交给 Spring 容器就可以实现自定义了。但是这种方式不好的地方就是只能用一种 PasswordEncoder 进行密码比对,好处就是专一、密码前不用写{id}。

测试案例:

配置代码

@EnableWebSecurity
public class SecurityConfig {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic InMemoryUserDetailsManager inMemoryUserDetailsManager(){return new InMemoryUserDetailsManager(User.withUsername("root").password("$2a$10$0BBFHiDx9jmix3FldDvFYexYGrOascxKcDagaG1wW7LpnPeQIjBca").roles("admin").build());}@Beanpublic AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(inMemoryUserDetailsManager()).and().build();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginProcessingUrl("/api/auth/login").usernameParameter("username").passwordParameter("password").and().logout().logoutUrl("/api/auth/logout").and().csrf().disable().build();}}

测试

请添加图片描述

三、总结

  • 有些会有疑问:我不写 {id} 呢?会怎么密码比对呢?其实源码分析的时候很清楚了,如果没有匹配到委派方案,就会按DelegatingPasswordEncoder 中的默认方案,即会抛出java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"的异常。
  • DelegatingPasswordEncoder 实例对象是通过 PasswordEncoderFactories 类中的 createPasswordEncoder 方法进行创建的。方法内可以查看对应的 id ,所以这个类需要记住。
  • 自定义加密通过源码分析可以得出两种自定义的方案:
    • 使用 {id} 的形式,即在密码前加上 {id},例如:{noop}123,即表示等密码比对的时候用 NoOpPasswordEncoder 中的matches方法进行比对。
    • 向 Spring 容器中注入 PasswordEncoder 实例,这样Spring Security 会选择注入的实例去进行密码的比对,缺点是整个项目的密码比对都只能采用这种比对方案。
  • 对两种自定义加密方式有疑惑的,可以看上面对DefaultPasswordEncoderAuthenticationManagerBuilder 、LazyPasswordEncoder 的源码分析。在LazyPasswordEncoder中解释了getPasswordEncoder方法,是先从Spring容器中找,再去拿 PasswordEncoderFactories 工厂去创建。

下面是咱从 HttpSecurity 中获取的AuthenticationManagerBuilder实例,内部属性可以看看。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/350721.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

花3个月面过阿里测开岗,拿个25K不过分吧?

计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在一家初创…

Excel集成GPT,惊呆我了

Excel&GPT 从最开始的GPT对话&#xff0c;到后面的Office集成GPT进行内测&#xff0c;用GPT实现写Word、做Excel、做PPT&#xff08;别着急&#xff0c;后面我会分享AIPPT&#xff09;已经不再是设想&#xff0c;而在逐步演变成真的&#xff01; 当然&#xff0c;目前国内…

impala内存超限

目录 一、背景 二、报错内容 三、解决办法 1.调参 2.简单粗暴 一、背景 impala shell执行SQL语句时报错 二、报错内容 Memory limit exceeded: Could not allocate memory while trying to increase reservation. 三、解决办法 1.调参 mem_limit参数&#xff1a;&…

2023,智能硬件的AIGC“又一春”

​ 文|智能相对论 作者|佘凯文 消费电子产品风光不再&#xff0c;特别是自去年以来&#xff0c;电子消费市场经历了一整年的寒潮袭击&#xff0c;智能手机等产品达到10年消费谷底&#xff0c;PC出货量整体下降16%&#xff0c;不仅如此&#xff0c;包括平板、可穿戴设备也一改…

Java读取U盘的PID、VID、SN

Java是不能像C、C那样直接读取U盘的PID、VID、SN信息的&#xff0c;但是我们可以换一个思路&#xff0c;让Java从注册表中读取信息。 这是U盘信息在注册表中的位置&#xff1a;HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\USBSTOR\\Enum import java.io.Buffer…

02_搭建项目(servlet+java bean+jsp的商城教程)

搭建项目 数据库和表项目包结构jar包工具类和配置文件前台文件&#xff08;html,css,js,img等静态资源&#xff09; 1&#xff09;数据库和表 新建数据库store&#xff0c;按照上一节分析的实体结构创建数据表并插入数据&#xff1a; user表&#xff1a; CREATE TABLE user …

org.apache.jasper.JasperException: Unable to compile class for JSP

博主最近在将一个项目导入一台新机器的时候&#xff0c;出现了如题所述的异常&#xff0c;以前从没有出现过&#xff0c;我跟往常一样到网上寻求帮助&#xff0c;网上的解决方案多如牛毛&#xff0c;什么Tomcat版本与IDE不匹配、jar包冲突、xml文件错误、路径错误等等。在一一尝…

Java毕业设计:基于jsp+mysql+Spring+SpringMVC+mybatis的网络硬硬盘系統网站

项目介绍 网盘&#xff0c;又称网络U盘、网络硬盘&#xff0c;是一些网络公司推出的在线存储服务。向用户提供文件的存储、访问、备份、共享等文件管理功能&#xff0c;使用起来十分方便。不花钱的移动硬盘。用户可以把网盘看成一个放在网络上的硬盘或U盘&#xff0c;不管你是…

基于jsp+mysql+ssm网络硬硬盘系統网站-计算机毕业设计

项目介绍 网盘&#xff0c;又称网络U盘、网络硬盘&#xff0c;是一些网络公司推出的在线存储服务。向用户提供文件的存储、访问、备份、共享等文件管理功能&#xff0c;使用起来十分方便。不花钱的移动硬盘。用户可以把网盘看成一个放在网络上的硬盘或U盘&#xff0c;不管你是…

supermicro服务器通过U盘安装部署vmware vsphere并使用

制作U盘安装镜像 前提准备&#xff1a; 镜像&#xff1a; https://my.vmware.com/en/web/vmware/evalcenter?pfree-esxi6 直接到官网下载 VMware vSphere Hypervisor 6.7&#xff0c;不过要先注册才能用。另外这个是免费版的&#xff0c;有验证码&#xff0c;可以长期使用…

linux redhat9 u盘,LINUX(redhat9)下安装JDK(虚拟机VM上挂载U盘)

LINUX(redhat9)下安装JDK(虚拟机VM上挂载U盘) 2010-07-11 21:24 1.首先&#xff0c;我们去http://www.doczj.com/doc/11c2bbedb8f67c1cfad6b82a.html去下载JDK1.6.0 for LINUX的rpm. (http://www.doczj.com/doc/11c2bbedb8f67c1cfad6b82a.html/javase/downloads/index.jsp) 应该…

Ubuntu的JSP服务器安装

2019独角兽企业重金招聘Python工程师标准>>> 一、man中文版 1、安装&#xff1a;sudo apt-get install manpages-zh 2、把中文man包转换成utf8格式的 新建一个脚本文件gedit t.sh把下面内容添加进去#!/bin/bash cd /usr/share/man/zh_CN/ for k in *do cd $k for i …

JSP环境配置全解!

实现JSP文件环境配置除了MyEclipse外&#xff0c;最通俗的还是jdk加Tomcat。 今天上午刚把期末考试搞定&#xff0c;就紧迫不及待的在李超和超哥的指导下完成了JSP环境配置。以后要专心搞JAVA了&#xff0c;呵呵。。。 为了让我以后可以快速的独立配置环境…

关于电脑格式化之后的恢复

前言&#xff1a;在这里并没有用OneDrive备份的文件&#xff0c;为了极致的纯净&#xff0c;但是为了恢复基础资源的速度&#xff0c;建议用U盘或者百度云把TomCat和javeEE的文件夹上传一下&#xff0c;省的后面网站下的太慢 1.格式化电脑 桌面右键——》显示设置——》Window…

亚马逊云科技出海日倒计时,潮向新世界,亮点抢先知

数字化浪潮之下&#xff0c;中国企业的全球化步伐明显提速。从“借帆出海”到“生而全球化”&#xff0c;中国企业实现了从传统制造业“中国产品”出口&#xff0c;向创新“中国技术”和先导“中国品牌”的逐步升级。 作为全球云计算的开创者与引领者&#xff0c;亚马逊云科技…

使用jsp完成商品列表的动态显示

1数据库准备工作 1创建数据库 2 创建product表 代码如下&#xff1a; CREATE TABLE product (pid varchar(50) NOT NULL,pname varchar(50) DEFAULT NULL,market_price double DEFAULT NULL,shop_price double DEFAULT NULL,pimage varchar(200) DEFAULT NULL,pdate date DEFAU…

基于JSP的网络硬盘

1 可行性分析 web开发技术是Internet应用的一个重要方而&#xff0c;而JSP又是web开发的最先进的技术&#xff0c;是当前web开发人员的首选技术。 随着网络技术的日益普及和信息化建设的重视&#xff0c;网络硬盘是一种新型安全的网络存储系统&#xff0c;已越来越受到.人们的重…

JavaWeb之动态页面技术jsp显示商品列表

一、新建商品数据库 CREATE TABLE product (pid varchar(50) NOT NULL,pname varchar(50) DEFAULT NULL,market_price double DEFAULT NULL,shop_price double DEFAULT NULL,pimage varchar(200) DEFAULT NULL,pdate date DEFAULT NULL,is_hot int(11) DEFAULT NULL,pdesc varc…

如何用Python语言调取百度翻译的API

诸神缄默不语-个人CSDN博文目录 本文介绍如何用Python语言调用百度翻译的API服务。 理论上类似的写法也可以应用于其他语言。 http://api.fanyi.baidu.com/manage/developer可以注册开发者&#xff0c;或者APP ID和秘钥&#xff1b;然后还可以进行开发者认证&#xff0c;获得…

中国振华刘昕:携手用友打造电子行业的数智化平台,服务全行业

近期&#xff0c;一年一度的用友BIP技术大会圆满召开。来自行业领先企业的CIO/CDO、生态伙伴、开发者、分析师、媒体等共聚北京用友产业园&#xff0c;了解最新技术发展趋势、探讨行业热点话题。期间&#xff0c;中国振华电子集团有限公司信息中心主任兼研究院副院长刘昕在数科…