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 {@Resourceprivate UserDetailsService userDetailsService;@Autowiredpublic void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());}@Beanpublic 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 {@Overridepublic String encode(CharSequence rawPassword) {// 直接抛出异常throw new UnsupportedOperationException("encode is not supported");}@Overridepublic 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 {@Overridepublic String encode(CharSequence rawPassword) {return hashWithSha512(rawPassword.toString());}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {String hashedPassword = encode(rawPassword);return encodedPassword.equals(hashedPassword);}@Overridepublic 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DOS 操作系统

DOS 介绍 DOS&#xff1a;disk operating system&#xff0c;磁盘操作系统。 中国DOS联盟下载 MS-DOS 7.10完整安装版&#xff08;含图形安装程序&#xff09; DOS 环境下的操作 输入部分内容后按下 Tab 可以快速自动补全。 按住 Ctrl 键可以用鼠标滚轮改变字号大小。 DO…

【数据结构初阶】--- 顺序表

顺序表&#xff0c;好像学C语言时从来没听过&#xff0c;实际上就是给数组穿了层衣服&#xff0c;本质是一模一样的。 这里的顺序表实际是定义了一个结构体&#xff0c;设计各种函数来实现它的功能&#xff0c;比如说数组中的增删改查插入&#xff0c;这些基本操作其实平时就会…

c++调用动态库LNK2019无法解析的外部符号LNK1120无法解析的外部命令

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK1120 6 个无法解析的外部命令 ConsoleApplication1 D:\vs_qt_project\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe 1 严重性 代码 说明 项目 文件 行 …

应用层——HTTP协议(自己实现一个http协议)——客户端(浏览器)的请求做反序列化和请求分析,然后创建http向响应结构

应用层&#xff1a;之前我们写的创建套接字&#xff0c;发送数据&#xff0c;序列化反序列化这些都是在写应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 之前的网络计算机是我们自定义的协议&#xff1a;传输的数据最终是什么样的结…

高级文件操作

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python内置的os模块除了可以对目录进行操作&#xff0c;还可以对文件进行一些高级操作&#xff0c;具体函数如表4所示。 表4 os模块提供的与文件相…

【AI基础】第三步:纯天然保姆喂饭级-安装并运行chatglm2-6b

chatglm2构建时使用了RUST&#xff0c;所以在安装chatglm2之前&#xff0c;先安装RUST。 此系列文章列表&#xff1a; 【AI基础】第一步&#xff1a;安装python开发环境-windows篇_下载安装ai环境python-CSDN博客 【AI基础】第一步&#xff1a;安装python开发环境-conda篇_mini…

知识图谱的应用---智慧司法

文章目录 智慧司法典型应用 智慧司法 智慧司法是综合运用人工智能、大数据、互联网、物联网、云计算等信息技术手段&#xff0c;遵循司法公开、公平、公正的原则&#xff0c;与司法领域业务知识经验深度融合&#xff0c;使司法机关在审判、检查、侦查、监管职能各方面得到全面的…

【数据结构】图论入门

引入 数据的逻辑结构&#xff1a; 集合&#xff1a;数据元素间除“同属于一个集合”外&#xff0c;无其他关系线性结构&#xff1a;一个对多个&#xff0c;例如&#xff1a;线性表、栈、队列树形结构&#xff1a;一个对多个&#xff0c;例如&#xff1a;树图形结构&#xff1…

SpringBoot中的WebMvcConfigurationSupport和WebMvcConfigurer

在SpringBoot中可以通过以下两种方式来完成自定义WebMvc的配置&#xff1a; &#xff08;1&#xff09;继承WebMvcConfigurationSupport类 &#xff08;2&#xff09;实现WebMvcConfigurer接口 通过这两种方式完成的WebMvc配置存在差异&#xff0c;本文将对此作简单说明与区…

【Vue】单页应用程序介绍

通常基于Vue去开发一整个网站&#xff0c;开发出来的这整个网站应用&#xff0c;我们都会叫做单页应用程序 概念 单页应用程序&#xff1a;SPA【Single Page Application】是指所有的功能都在一个html页面上实现 我们可以将页面共用的部分封装成组件&#xff0c;底下要切换的也…

工具-金舟投屏软件: 手机如何投屏到电脑上 / Wi-Fi / USB

金舟安卓/iOS苹果投屏-正版软件下载中心 方法一、金舟投屏软件-wifi 1.1、准备工作 确保苹果手机和Windows电脑都连接到同一个Wi-Fi网络。 在Windows电脑上安装并打开金舟投屏软件。 1.2、操作步骤 在金舟投屏软件上选择“苹果手机投屏”功能。 在苹果手机上下滑屏幕&am…

New Work-flow of Circuit Bootstrapping

参考文献&#xff1a; [CGGI17] Chillotti I, Gama N, Georgieva M, et al. Faster packed homomorphic operations and efficient circuit bootstrapping for TFHE. ASIACRYPT 2017 (1): 377-408.[CDKS21] Chen H, Dai W, Kim M, et al. Efficient homomorphic conversion be…

dots_image 增强图像中的圆点特征

dots_image 增强图像中的圆点特征 1. dot_image 有什么用途&#xff1f;2. 点状字符的特征增强3. Halcon代码 1. dot_image 有什么用途&#xff1f; Enhance circular dots in an image. 这个算子可以增强图像中的圆点特征&#xff0c;例如下面的例子。 2. 点状字符的特征增强…

忆恒创源国产系列新品 —— PBlaze7 7A40 取得 PCI-SIG 兼容性认证

在此前报道中&#xff0c;我们曾预告了忆恒创源国产系列 PCIe 5.0 SSD 新品 —— PBlaze7 7A40&#xff0c;今天&#xff0c;这款 SSD 已经顺利通过 PCI-SIG 的严格测试并出现在 Integrators List 集成商列表当中&#xff0c;标志着距离 PBlaze7 7A40 的正式发布又近了一步。 正…

记录汇川:红绿灯与HMI-ST

项目要求&#xff1a; 子程序&#xff1a; 子程序&#xff1a; 实际动作如下&#xff1a; 红绿灯与HMI-ST

STM32项目分享:智能门禁锁系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板及元器件图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.c…

基于某评论的TF-IDF下的LDA主题模型分析

完整代码&#xff1a; import numpy as np import re import pandas as pd import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import LatentDirichletAllocationdf1 pd.read_csv(小红书评论.csv) # 读取同目录下csv文件…

【Vue】组件化开发

文章目录 一、介绍二、根组件 App.vue 一、介绍 组件化&#xff1a;一个页面可以拆分成一个个组件&#xff0c;每个组件有着自己独立的结构、样式、行为。 好处&#xff1a;便于维护&#xff0c;利于复用 → 提升开发效率。 组件分类&#xff1a;普通组件、根组件。 根组件…

MySQL 高级 - 第十一章 | 索引优化与查询优化

目录 第十一章 索引优化与查询优化11.1 数据准备11.2 索引失效案例11.2.1 全值匹配10.2.2 最佳左前缀法则10.2.3 主键插入顺序10.2.4 计算、函数、类型转换&#xff08;自动或手动&#xff09;导致索引失效10.2.5 范围条件右边的列索引失效10.2.6 不等于&#xff08;! 或者 <…

删除目录

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 删除目录可以通过使用os模块提供的rmdir()函数实现。通过rmdir()函数删除目录时&#xff0c;只有当要删除的目录为空时才起作用。rmdir()函数的基本语…