从0到1掌握 Spring Security(第四篇):密码加密原理、默认行为与配置选型

🎏:你只管努力,剩下的交给时间
🏠 :小破站
从0到1掌握 Spring Security(第四篇):密码加密原理、默认行为与配置选型
- 摘要
- 为什么必须“加密”(准确说是单向散列)?🧠
- 默认行为(不配置时会怎样)📦
- 本项目当前的选择与演示 🔭
- 各可选方案与如何配置 🧰
- 方案A:只用 BCrypt(工程上最常用)
- 方案B:DelegatingPasswordEncoder(多算法兼容)
- 方案C:PBKDF2(NIST 标准族)
- 方案D:SCrypt(内存密集型,抗 ASIC)
- 如何验证与体验(本项目)🕹️
- 参数与性能建议 ⚖️
- 密码升级(从旧算法迁移到新算法)🔄
- 常见坑与规避 🧯
- 在本项目怎么“换一种方式” 🛠️
- 小结 ✅
- 感谢
摘要
- 讲清楚三件事:为什么必须加密、Spring Security/Boot 的默认行为、可选的加密算法与如何配置
- 本项目已默认使用 BCrypt;提供演示页“密码加密对比”,控制台可直观看到每种算法的表现
- 给出严谨的选型建议与“密码升级”方案,避免模糊/不实描述
为什么必须“加密”(准确说是单向散列)?🧠
- 明文泄露=用户全线沦陷:大量用户跨站点复用密码
- 撞库/字典/彩虹表:攻击者用常见口令或预计算表进行快速匹配
- 不可逆+盐+慢:合格的口令存储必须具备
- 不可逆(单向散列)
- 随机盐(每个用户不同,抵御彩虹表)
- 计算“有点慢”(加大攻击成本,可调“工作因子”)
对密码“加密”和“散列”的习惯混称在工程里常见,但用于口令存储的应是单向散列算法(BCrypt/PBKDF2/SCrypt/Argon2 等),而非可逆加密。
默认行为(不配置时会怎样)📦
- Spring Security 推荐的“默认方案”是 DelegatingPasswordEncoder(多算法前缀格式),其默认 id 为 bcrypt。带前缀的存储形如:
{bcrypt}$2a101010……
、{pbkdf2}……
、{scrypt}……
- Spring Boot 内置的属性用户(spring.security.user.password)如果你只写明文,Boot 会自动加
{noop}
前缀(仅用于快速演示,不推荐上线) - DaoAuthenticationProvider 校验规则
- 根据密文前缀
{id}
选用对应算法进行 matches(raw, encoded) - 没有前缀时,依赖你提供的 PasswordEncoder 决定如何处理
- 根据密文前缀
本项目在 config 模式下也会对 yml 中的密码进行编码保存,不以明文入库/入内存,因此不是 {noop}。
本项目当前的选择与演示 🔭
- 我们在 SecurityConfig 中声明了 BCryptPasswordEncoder,作为全局 PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}
-
三种认证来源(config/memory/database)最终都会走同一个 PasswordEncoder,因此:
- 配置文件模式:启动时将 yml 的明文密码编码为 BCrypt
- 内存模式:初始化用户时使用 BCrypt 编码
- 数据库模式:入库即为 BCrypt 密文
-
直观演示(控制台)
- 启动自动打印“当前加密演示”,以及在页面“/password-demo”可一键对比:
- BCrypt(推荐)
- PBKDF2
- SCrypt(已添加 BouncyCastle 依赖)
- NoOp(仅测试)
- DelegatingPasswordEncoder(多算法前缀)
- 启动自动打印“当前加密演示”,以及在页面“/password-demo”可一键对比:
各可选方案与如何配置 🧰
方案A:只用 BCrypt(工程上最常用)
- 特点:自带盐、每次结果不同、工作因子可调(cost)
- 配置:保持当前 Bean 即可(推荐 cost 10–12,根据机器调整)
方案B:DelegatingPasswordEncoder(多算法兼容)
- 适合渐进迁移(旧用户用旧算法,新用户用新算法)
- 创建方式:PasswordEncoderFactories.createDelegatingPasswordEncoder()
- 存储格式带
{id}
前缀,按前缀自动选择算法
String id = "bcrypt";
Map<String, PasswordEncoder> m = Map.of("bcrypt", new BCryptPasswordEncoder(),"pbkdf2", new Pbkdf2PasswordEncoder("", 8, 185000, Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256),"scrypt", new SCryptPasswordEncoder(16384,8,1,32,64)
);
PasswordEncoder delegating = new DelegatingPasswordEncoder(id, m);
方案C:PBKDF2(NIST 标准族)
- 可配置迭代次数,较通用
- 本项目示例已使用 SHA-256 变体
方案D:SCrypt(内存密集型,抗 ASIC)
- 更抗暴力破解,但依赖更复杂(我们已加入 BouncyCastle)
- 依赖(已添加):
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk18on</artifactId><version>1.77</version>
</dependency>
注:Argon2 亦是优秀选择,但当前项目未演示,可在需要时加。
如何验证与体验(本项目)🕹️
- 启动后访问:/password-demo
- “当前项目密码加密方式”:看你现在的 PasswordEncoder 行为
- “对比所有加密方式”:一次性打印 BCrypt/PBKDF2/SCrypt/NoOp/Delegating 的加密与校验结果
- “密码强度”“加密性能”:帮助理解“慢即是安全”的含义
- 控制台可见典型输出(两次加密不同 → 有盐;matches 为 true → 校验通过)
参数与性能建议 ⚖️
- BCrypt cost(工作因子)建议 10–12(生产按 CPU 调整,以 100ms 级别为参考)
- PBKDF2 迭代次数 150k–300k(随硬件演进调高)
- SCrypt 参数(N=16384,r=8,p=1)为常见基线,按内存与性能压测校准
- 目标是让“合法认证体验可接受、暴力破解代价高昂”
密码升级(从旧算法迁移到新算法)🔄
- 场景:历史上存过明文/MD5/低 cost 的 BCrypt,希望“用户无感升级”
- Spring Security 支持:UserDetailsPasswordService
- 认证成功后,若 PasswordEncoder.upgradeEncoding(encoded) 返回 true,则回调 updatePassword 写回新密文
- 推荐实践:在 database 模式下实现一个最小的 UserDetailsPasswordService,把新编码落库(与当前 UserRepository 配合即可)
常见坑与规避 🧯
- 二次加密:重复 encode 导致永远匹配失败。规范:持久化前 encode;校验用 matches(raw, encoded)
- 明文入库:严禁。演示专用 NoOp 切勿上线
- 角色与权限映射:hasRole(“ADMIN”) → 实际校验 “ROLE_ADMIN”(本项目已正确映射)
- 同一盐/固定盐:不可取。应依赖算法自身随机盐(如 BCrypt)
- 算法迁移:使用 Delegating 前缀存储 + UserDetailsPasswordService 平滑升级
在本项目怎么“换一种方式” 🛠️
- 保持“仅 BCrypt”:什么都不改(当前即如此)
- 切换到 Delegating(多算法并行/迁移):
- 将 PasswordEncoder Bean 改为 DelegatingPasswordEncoder
- 旧密文前缀化或在数据库中逐步替换
- 可选:实现 UserDetailsPasswordService,登录即升级
小结 ✅
- 必须用单向散列存储密码:不可逆+随机盐+可调慢
- Spring Security 的推荐默认是 DelegatingPasswordEncoder(默认 bcrypt)
- 本项目已采用 BCrypt 并提供对比演示页,便于你直观理解不同算法的行为
- 若要面向未来做升级与兼容,优先考虑 Delegating + UserDetailsPasswordService
感谢
感谢你读到这里,说明你已经成功地忍受了我的文字考验!🎉
希望这篇文章没有让你想砸电脑,也没有让你打瞌睡。
如果有一点点收获,那我就心满意足了。
未来的路还长,愿你
遇见难题不慌张,遇见bug不抓狂,遇见好内容常回访。
记得给自己多一点耐心,多一点幽默感,毕竟生活已经够严肃了。
如果你有想法、吐槽或者想一起讨论的,欢迎留言,咱们一起玩转技术,笑对人生!😄
祝你代码无bug,生活多彩,心情常青!🚀