Spring Security系列之PasswordEncoder

概述

任何一个登录系统的密码不能明文存储,万一发生数据库泄漏事故(不管是内部人员导出数据库数据还是被黑客攻击破解数据库实例节点拿到数据库数据等,又或者是其他情况造成的),将产生巨大的损失。因此明文密码在存储到数据库之前需要加密处理。

加密算法有很多,大致有如下分类:

  • 哈希函数算法:包括消息摘要算法(MD4,MD5等),消息摘要算法是一种特殊类型的哈希函数算法,用于将任意长度的数据映射为固定长度的哈希值或摘要。摘要值通常用于验证数据的完整性、数字签名、身份验证等用途。算法包括:
    • MD5(Message Digest Algorithm 5):已经不推荐使用,存在碰撞攻击漏洞
    • SHA-1(Secure Hash Algorithm 1):也存在碰撞攻击漏洞,逐渐被淘汰
    • SHA-256、SHA-384、SHA-512:SHA-2系列,目前被广泛应用,提供更高的安全性
  • 对称加密算法使用相同的密钥进行加密和解密。常见的对称加密算法包括:
    • DES(Data Encryption Standard):已经不推荐使用,因为密钥长度较短易受到攻击
    • 3DES(Triple DES):DES增强版,使用三个密钥提高安全性
    • AES(Advanced Encryption Standard):目前广泛应用的对称加密算法,具有较高的安全性和性能
  • 非对称加密算法(公钥加密算法):
    非对称加密算法使用一对密钥,分别是公钥和私钥,公钥用于加密,私钥用于解密。常见的非对称加密算法包括:
  • RSA(Rivest-Shamir-Adleman):基于大数分解难题,被广泛用于数字签名和密钥交换
  • ECC(Elliptic Curve Cryptography):利用椭圆曲线上的离散对数问题,相比RSA,提供相同安全级别下更短的密钥长度和更高的性能

反查表、彩虹表

上文提到一些已经不推荐使用、逐渐被淘汰的算法,如MD5、SHA-1。因为不管是MD5还是SHA-1算法,对于给定的某个字符串(密码),经过哈希函数计算之后得到的结果都是固定的。比如admin经过MD5计算(有16位和32位之分,这里用的是16位)结果始终是7a57a5a743894a0eroot经过SHA-1计算后结果始终是dc76e9f0c0006e8f919e0c515c66dbba3982f785

那黑客们就可以维护一个数据库,其字段包括加密后的密文、加密算法、明文密码,意味着可以根据密文反查明文密码。这就是反查表。

基于反查表,黑客们后来发明更高级的彩虹表。

在Java Web开发中,我们会遇到各种各样的安全问题。作为最基本的,数据库密码的安全性如何得到保证呢?此时Spring Security隆重登场,可以帮助我们解决这个问题。

Spring Security

实例

加密密码的配置类(代码片段):

import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity()
public class WebSecurityConfig implements SecurityFilterChain {
	@Resource
	private UserDetailsService userDetailsService;
	
	@Autowired
	public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
		authenticationManagerBuilder
			.userDetailsService(this.userDetailsService)
			.passwordEncoder(passwordEncoder());
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

PasswordEncoder

PasswordEncoder接口定义如下:

public interface PasswordEncoder {
	// 用来对明文密码进行加密
	String encode(CharSequence rawPassword);
	// 用来进行密码比对
	boolean matches(CharSequence rawPassword, String encodedPassword);
	// 用来判断当前密码是否需要升级,默认返回false表示不需要升级
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}
}

尚未废弃的实现类,都是自适应单向函数(Adaptive One-way Functions)来处理密码问题,这种函数在进行密码匹配时,会有意占用大量系统资源(例如CPU、内存等),可以增加恶意用户攻击系统的难度。包括:bcrypt、PBKDF2、scrypt以及argon2。

因此实现类包括:

  • BCryptPasswordEncoder:使用bcrypt强散列算法对密码进行加密,为提高密码的安全性,bcrypt算法故意降低运行速度,以增强密码破解的难度。BCryptPasswordEncoder自带salt加盐机制,即使相同的明文每次生成的加密字符串都不相同。默认强度为10(参考源码里的strength字段),开发者可以根据自己的服务器性能进行调整,以确保密码验证时间约为1秒钟(官方建议密码验证时间为1秒钟,既可以提高系统安全性,又不会过多影响系统运行性能)
  • Argon2PasswordEncoder:使用Argon2算法对密码进行加密,Argon2曾在Password Hashing Competition竞赛中获胜。为了解决在定制硬件上密码容易被破解的问题,Argon2也是故意降低运算速度,同时需要大量内存
  • Pbkdf2PasswordEncoder:使用PBKDF2算法对密码进行加密,可用于FIPS(Federal Information Processing Standard,美国联邦信息处理标准)认证
  • SCryptPasswordEncoder:使用scrypt算法对密码进行加密

几个已经被废弃的基于消息摘要算法的实现类:

  • NoOpPasswordEncoder:密码明文存储,不可用于生产环境
  • Md4PasswordEncoder:使用Md4算法加密密码
  • LdapShaPasswordEncoder:使用SHA算法
  • StandardPasswordEncoder:使用SHA-256算法
  • MessageDigestPasswordEncoder:使用MD5算法

BCryptPasswordEncoder

public String encode(CharSequence rawPassword) {
	if (rawPassword == null) {
		throw new IllegalArgumentException("rawPassword cannot be null");
	}
	String salt = getSalt();
	return BCrypt.hashpw(rawPassword.toString(), salt);
}

private String getSalt() {
	if (this.random != null) {
		return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
	}
	return BCrypt.gensalt(this.version.getVersion(), this.strength);
}

使用Spring Security提供的BCrypt工具类生成盐(salt);然后,根据盐和明文密码生成最终的密文。所谓加盐,就是在初始化明文数据时,由系统自动向该明文里添加一些附加数据,然后散列。引入加盐机制的目的是进一步提高加密数据的安全性,单向散列加密及加盐思想广泛应用于系统登录过程中的密码生成和校验。

构造方法:

public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
	if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
		throw new IllegalArgumentException("Bad strength");
	}
	this.version = version;
	this.strength = (strength == -1) ? 10 : strength;
	this.random = random;
}

从构造函数可知,strength长度默认为10,最小值为BCrypt.MIN_LOG_ROUNDS=4,最大值为BCrypt.MAX_LOG_ROUNDS=31。显而易见,长度越长,加密算法越复杂,被恶意破解攻击的难度越大,但是也会增加系统负载,增加加密计算时长和存储空间。因此需要取得权衡,默认情况下使用Spring Security建议的长度10即可。

PasswordEncoderFactories

浏览一下spring-security-crypto-6.2.3源码结构:
在这里插入图片描述
不难发现PasswordEncoderFactories这个类,采用工厂方法模式,源码:

public static PasswordEncoder createDelegatingPasswordEncoder() {
	String encodingId = "bcrypt";
	Map<String, PasswordEncoder> encoders = new HashMap();
	encoders.put(encodingId, new BCryptPasswordEncoder());
	encoders.put("ldap", new LdapShaPasswordEncoder());
	encoders.put("MD4", new Md4PasswordEncoder());
	encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
	encoders.put("noop", NoOpPasswordEncoder.getInstance());
	encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
	encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
	encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
	encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
	encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
	encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
	encoders.put("sha256", new StandardPasswordEncoder());
	encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
	encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
	return new DelegatingPasswordEncoder(encodingId, encoders);
}

静态方法createDelegatingPasswordEncoder,encoders中存储每一种密码加密方案的id和所对应的加密类,如bcrypt对应BcryptPassword。最后,返回代理类DelegatingPasswordEncoder实例,并且默认使用的加密方案是BCryptPasswordEncoder。

DelegatingPasswordEncoder

DelegatingPasswordEncoder,采用代理模式,Spring Security 5.0版本后默认的密码加密方案,主要考虑如下三方面的因素:

  • 兼容性:使用DelegatingPasswordEncoder可以帮助许多使用旧密码加密方式的系统顺利迁移到Spring Security中,它允许在同一个系统中同时存在多种不同的密码加密方案
  • 便捷性:密码存储的最佳方案不可能一直不变,使用DelegatingPasswordEncoder作为默认的密码加密方案,当需要修改加密方案时,只需要修改很小一部分代码即可实现
  • 稳定性:作为一个框架,Spring Security不能经常进行重大更改,使用Delegating PasswordEncoder可以方便地对密码进行升级(自动从一个加密方案升级到另外一个加密方案)

属性如下:

// 默认的前缀和后缀,用于包裹将来生成的加密方案的id
private static final String DEFAULT_ID_PREFIX = "{";
private static final String DEFAULT_ID_SUFFIX = "}";
// 构造方法里支持传入用户自定义的前缀和后缀
private final String idPrefix;
private final String idSuffix;
// 默认的加密方案id
private final String idForEncode;
// 根据idForEncode从idToPasswordEncoder map中提取出来的
private final PasswordEncoder passwordEncoderForEncode;
// 保存id和加密方案之间的映射
private final Map<String, PasswordEncoder> idToPasswordEncoder;
// 默认的密码比对器,当根据密码加密方案的id无法找到对应的加密方案时,就会使用默认的密码比对器。默认类型是UnmappedIdPasswordEncoder
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();

UnmappedIdPasswordEncoder是一个内部私有类:

private class UnmappedIdPasswordEncoder implements PasswordEncoder {
	@Override
	public String encode(CharSequence rawPassword) {
		// 直接抛出异常
		throw new UnsupportedOperationException("encode is not supported");
	}

	@Override
	public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
		// 并不会做任何密码比对操作,直接抛出异常
		String id = extractId(prefixEncodedPassword);
		throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
	}
}

核心方法encode

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

作为一个代理类,不负责具体的加密工作,由加密类来完成,最后加上类似于{bcrypt}这样的前缀,不同的前缀表示使用不同的加密算法,即不同的PasswordEncoder实现类,当然也包括自定义的加密类。

核心方法matches

public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
	if (rawPassword == null && prefixEncodedPassword == null) {
		return true;
	}
	String id = extractId(prefixEncodedPassword);
	PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
	if (delegate == null) {
		return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
	}
	String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
	return delegate.matches(rawPassword, encodedPassword);
}

extractId方法用于从加密字符串中提取出具体的加密方案id,也就是前缀和后缀包裹的字符串,如bcrypt,此方法就不贴出来了。根据加密方案id从map集合查找对应的加密算法实现类,查找失败则使用默认的加密类,即UnmappedIdPasswordEncoder,然后就会抛出异常。

核心方法upgradeEncoding

public boolean upgradeEncoding(String prefixEncodedPassword) {
	String id = extractId(prefixEncodedPassword);
	if (!this.idForEncode.equalsIgnoreCase(id)) {
		return true;
	} else {
		String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
		return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
	}
}

如果当前加密字符串所采用的加密方案不是默认的BcryptPasswordEncoder ,就会自动进行密码升级,否则就调用默认加密方案的upgradeEncoding方法判断密码是否需要升级。

自定义加密方案

业务开发中,如果Spring Security自带的几个加密类都不能满足需求,或者业务场景比较复杂,需要兼容数据库历史未加密字段或加密算法不够好的字段,则可能需要自定义加密类。

具体来说,实现PasswordEncoder接口类,并重写3个方法。比如自定义一个使用SHA-512加密算法的加密类:

public class Sha512PasswordEncoder implements PasswordEncoder {
	@Override
	public String encode(CharSequence rawPassword) {
		return hashWithSha512(rawPassword.toString());
	}
	
	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		String hashedPassword = encode(rawPassword);
		return encodedPassword.equals(hashedPassword);
	}

	@Override
	public boolean upgradeEncoding(String prefixEncodedPassword) {
		// 不需要升级
		return false;
	}
	
	private String hashWithSha512(String input) {
		StringBuilder result = new StringBuilder();
		try {
			MessageDigest md = MessageDigest.getInstance("SHA-512");
			byte [] digested = md.digest(input.getBytes());
			for (int i = 0; i < digested.length; i++) {
				result.append(Integer.toHexString(0xFF & digested[i]));
			}
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("Bad algorithm");
		}
		return result.toString();
	}
}

最后需要配置一下使用此自定义类,使其生效。

参考

  • 深入浅出Spring Security

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

react-学习基础偏

1.新建文件夹 2.vscode引入这个文件夹 3.打开vscode终端 执行命令 npx create-react-app react-basic 创建基本项目&#xff08;react-basic项目文件夹名&#xff09; 4.进入到这个文件夹 可用的一些命令 这就算启动成功 5. 这是项目的核心包 渲染流程

关于JavaScript技术的基础内容汇总

目录 JavaScript 基础知识1. JavaScript 基本语法2. 变量和常量3. 数据类型4. 运算符5. 控制结构6. 函数7. 对象8. 数组9. 事件处理10. DOM 操作 JavaScript 基础知识 学习 JavaScript&#xff08;简称 JS&#xff09;是前端开发的重要组成部分&#xff0c;它是一种动态的、弱…

【c语言】指针就该这么学(3)

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C语言 目录 一、函数指针 1.函数指针变量的创建 2.函数指针变量的使用 二、typedef关键字 三、函数指针数组 1.函数指针数组的概念 2.函数指针数…

【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)

目录 一、 进程1.1 PID(进程标识符)1.2 内存指针1.3 文件描述符表1.4 状态1.5 优先级1.6 记账信息1.7 上下文 二、线程三、总结&#xff1a;进程和线程之间的区别&#xff08;非常非常非常重要&#xff0c;面试必考题&#xff09; 一、 进程 简单来介绍一下什么是进程&#xf…

[UE 虚幻引擎] DTLoadFbx 运行时加载FBX本地模型插件说明

本插件可以在打包后运行时动态加载FBX模型。 新建一个Actor 并添加一个 DT Runtime Fbx Component。 然后直接调用组件的函数 LoadFile 加载显示模型&#xff08;注&#xff1a;不支持模型动画&#xff09; FilePath : 加载模型的绝对路径。 Create Collision : 是否创建碰撞…

R语言探索与分析19-CPI的分析和研究

一、选题背景 CPI&#xff08;居民消费价格指数&#xff09;作为一个重要的宏观经济指标&#xff0c;扮演着评估通货膨胀和居民生活水平的关键角色。在湖北省这个经济活跃的地区&#xff0c;CPI的波动对于居民生活、企业经营以及政府宏观经济政策制定都具有重要的影响。因此&a…

单链表的排序

对一个单链表进行排序。 方法一&#xff1a;构造一个辅助的数组来排序。 Java构造一个集合来存储。先将链表内容存储到集合中去&#xff0c;再对集合进行排序&#xff0c;最后按照顺序取出集合中的数据即可。 public ListNode sortInLit(ListNode head) {if (head null || he…

Solon2分布式事件总线的应用价值探讨

随着现代软件系统的复杂性日益增加&#xff0c;微服务架构逐渐成为开发大型应用的主流选择。在这种架构下&#xff0c;服务之间的通信和协同变得至关重要。Solon2作为一个高性能的Java微服务框架&#xff0c;其分布式事件总线&#xff08;Distributed Event Bus&#xff09;为微…

国标GB/T 28181详解:国标GBT28181-2022的客户端主动发起历史视音频回放流程

目录 一、定义 二、作用 1、提供有效的数据回顾机制 2、增强监控系统的功能性 3、保障数据传输与存储的可靠性 4、实现精细化的操作与控制 5、促进监控系统的集成与发展 三、历史视音频回放的基本要求 四、命令流程 1、流程图 2、流程描述 五、协议接口 1、会话控…

网络空间安全数学基础·多项式环与有限域

5.1 多项式环&#xff08;掌握&#xff09; 5.2 多项式剩余类环&#xff08;理解&#xff09; 5.3 有限域&#xff08;熟练&#xff09; 5.1 多项式环 定义&#xff1a;设F是一个域&#xff0c;称是F上的一元多项式&#xff0e; 首项&#xff1a;如果an≠0&#xff0c;则称 a…

el-dialog给弹框标题后加图标,鼠标悬停显示详细内容

效果&#xff1a; 代码&#xff1a; <div slot"title" class"el-dialog__title">标题<el-tooltip effect"dark" placement"right"><div slot"content">鼠标悬停显示</div><i class"el-icon…

队列的讲解与实现

这里写目录标题 一、队列的概念及结构二、队列的实现(使用VS2022的C语言)1.初始化、销毁2.入队、出队3.返回队头元素、返回队尾元素、判空、返回有效元素个数 三、完整 Queue.c 源代码 一、队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端…

手机相册的排列方式探讨

不论你是不是程序员&#xff0c;你一定留意过一个问题&#xff1a;相册 App 基本都将图片裁剪成了居中的 1:1 正方形。那么手机相册 App&#xff0c;为什么要将图片切割成 1:1 正方形&#xff0c;然后以网格排列&#xff1f;是行业标准吗&#xff1f; 自适应图片宽度的图库&a…

vr样板房实景漫游展示制作解决了地产商难题

家具和软装销售中&#xff0c;如何直观展示产品优势一直是老板们的难题。口头描述往往难以让客户真正感受到产品的独特之处&#xff0c;这不仅影响了销售效果&#xff0c;也增加了沟通的难度。但现在&#xff0c;我们有了全新的解决方案——样板房VR全景编辑软件! 样板房VR全景…

SpringMVC:消息转换器

1. HttpMessageConvertor 简介 HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为&#xff1a;HTTP消息转换器。该接口下提供了很多实现类&#xff0c;不同的实现类有不同的转换方式。 转换器 如上图所示&#xff1a;HttpMessageConverter接口的可以将请求协议转…

数据结构之ArrayList与顺序表(上)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 顺序表的学习&#xff0c;点我 上面这篇博文是关于顺序表的基础知识&#xff0c;以及顺序表的实现。…

美团面试:百亿级分片,如何设计基因算法?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的架构类/设计类的场景题&#xff1a; 1.说说分库分表的基因算法&#xff1f…

【Linux】深入解析动静态库:原理、制作、使用与动态链接机制

文章目录 前言&#xff1a;1. 什么是动静态库2. 动静态库的制作和使用3. 动态库的查找问题4. 理解动态库的加载4.1. 站在系统的角度理解4.2. 编址、可执行程序4.3. 动态库动态链接和加载问题 总结&#xff1a; 前言&#xff1a; 在软件开发中&#xff0c;动静态库是两种重要的…

Netty中的ByteBuf使用介绍

ByteBuf有三类&#xff1a; 堆缓存区&#xff1a;JVM堆内存分配直接缓冲区&#xff1a;有计算机内存分配&#xff0c;JVM只是保留分配内存的地址信息&#xff0c;相对于堆内存方式较为昂贵&#xff1b;复合缓冲区&#xff1a;复合缓冲区CompositeByteBuf&#xff0c;它为多个B…

6月26~28日,2024北京国际消防展即将开幕!

随着社会的快速发展&#xff0c;消防安全日益受到广大民众的高度关注。为了进一步推动消防科技的创新与发展&#xff0c;提升全民消防安全意识&#xff0c;2024年北京消防展将于6月26日在北京国家会议中心盛大开展。目前:观众预登记已全面启动&#xff0c;广大市民和业界人士可…