AI 时代下的 Spring Security:为什么你的密码需要 {bcrypt} 前缀?

引言:从“能跑”到“安全”

背景:AI 能帮你写 BCryptPasswordEncoder,但它不会告诉你为什么旧系统升级会挂掉。

痛点:初创项目给个人使用时习惯直接明文密码,但是项目升级后安全领域也要进行相应的提升,而哈希值的保密程度在强大算力上有局限,现代主要采用自适应加密

核心机制:DelegatingPasswordEncoder (调度员模式)

主要流程

函数 验证密码(用户输入密码, 数据库存储的密码字符串):
    1. 检查 数据库密码字符串 是否以 "{id}" 开头
    2. 如果 没有前缀:
        抛出错误 "找不到加密器 ID"
    3. 提取 id (例如 "bcrypt")
    4. 从地图中找到对应的 加密器 (例如 BCryptPasswordEncoder)
    5. 使用找到的加密器 对比 (用户输入密码, 数据库密码字符串)
    6. 返回 对比结果 (true/false)

根据密码字符串开头的 {id} 标签,决定把任务派给哪个具体的加密器。

密码格式{id}encodedPassword

WHY:为什么这样设计

  • 平滑迁移:为了解决SpringSecurity5.0前后新旧密码共存的问题。
  • 未来proof:可自定义适配未来新型的加密器

HOW:如何正确配置

场景一:Spring Boot 默认(99% 的情况)

官方文档中提到SpringBoot已经默认配置好DelegatingPasswordEncoder

场景二:测试与演示

UserDetails user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

如需要创建多个用户,可以重复使用Builder,但是生产环境一定不要使用硬编码

场景三:遗留系统迁移

如果需要支持旧系统的明文或者 SHA-256,SpringSecurity引入DelegatingPasswordEncoder解决问题,根据特定的加密器自定义Map结合

登录情景

无前缀

有前缀 如 bcrypt

用户输入密码

数据库密码是否有前缀?

❌ 抛出异常: id null

提取 ID: bcrypt

查找映射表: Map.get(bcrypt)

委托 BCryptPasswordEncoder 比对

返回 true 或 false

@Bean
public PasswordEncoder passwordEncoder() {
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    // 新默认算法
    encoders.put("bcrypt", new BCryptPasswordEncoder());
    // 兼容旧系统明文 (迁移专用,用完即删)
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    // 兼容旧系统 SHA-256
    encoders.put("sha256", new StandardPasswordEncoder()); 
    
    // 关键点:第一个参数 "bcrypt" 表示新注册用户默认用这个
    return new DelegatingPasswordEncoder("bcrypt", encoders);
}

**常见陷阱与解决方案 **

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)

报错There is no PasswordEncoder mapped for the id "null"

原因:数据库里的密码缺少 {bcrypt} 前缀。

解法:批量更新数据库加上前缀

UPDATE users SET password = CONCAT('{bcrypt}', password) WHERE password NOT LIKE '{%}';

性能调优

  • BCrypt 的 strength 参数选多少?(建议:以登录耗时 ~0.5s-1s 为基准,通常 10-12,服务器强可更高)。

** 算法选型指南 **

  • BCrypt: 默认推荐,兼容性好,足够安全。
  • Argon2: 更现代,抗 GPU/ASIC 攻击能力更强,适合高安全需求场景(但需注意依赖库兼容性)。
  • PBKDF2: 老旧系统常用,FIPS 合规场景可能需要。

如果需要使用默认加密器以外的加密器,需要自己在pom文件中引入依赖

总结

AI 负责生成样板代码。你还是要负责:

  • 确认使用了 DelegatingPasswordEncoder
  • 检查数据库中是否有正确的 {id} 前缀。
  • 根据服务器性能调整加密强度。
  • 制定密码迁移策略。

选到与项目最适配的方案

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐