DelegatingPasswordEncoder通过{id}前缀自动识别密码算法,支持新旧格式共存;直接替换PasswordEncoder会导致旧密码验证失败;迁移需补前缀、注册旧编码器、登录时渐进重哈希。

可以做到,关键不是“换算法”,而是让新旧密码格式共存并自动识别——DelegatingPasswordEncoder 就是为此设计的,它不强制统一格式,而是靠前缀 {id} 区分用哪种解码器验证。
为什么直接替换 BCryptPasswordEncoder 会失败
如果把全局 PasswordEncoder 直接换成新的 BCryptPasswordEncoder(12),所有旧密码(比如用 BCryptPasswordEncoder(10) 或 MD5 存的)都会验证失败,因为哈希值不可逆、也无法跨参数比对。
- 用户登录时,框架拿新编码器去验旧哈希,必然
matches()返回false - 数据库里没存原始密码,无法批量重算
- 强制要求用户改密码才能登录,等于变相停服
DelegatingPasswordEncoder 的工作方式
它本身不加密,只看密码字符串开头的 {id} 前缀,再查表选对应编码器做验证。例如:
Word报告自动生成神器。自动生成专业Word文档报告,支持模板填充、目录生成、表格/图表插入、页眉页脚、多级标题、样式主题、批量邮件合并。当用户需要生成Word报告、创建合同文档、批量生成通知/证书、填充Word模板、制作商业计划书时触发。
{bcrypt}$2a$10$... → 交给 BCryptPasswordEncoder 处理
{pbkdf2}5d923b44... → 交给 Pbkdf2PasswordEncoder 处理
{noop}password → 交给 NoOpPasswordEncoder(明文,仅测试用)
- 必须确保所有已有密码都带合法前缀,否则
id解析为null,会抛IllegalArgumentException -
idForEncode参数只控制「新注册/改密时用哪种算法生成」,不影响旧密码验证逻辑 - 默认前缀是
{和},不建议改;自定义前缀需同步改所有存量密码字符串,风险极高
迁移旧密码的实操步骤
核心策略:登录成功后,检测密码是否为旧格式,若是则用新算法重哈希并更新数据库。整个过程对用户无感。
- 先确认当前数据库中密码字段是否已含前缀 —— 如果全是裸哈希(如
$2a$10$...),需先导出、补前缀(如批量加{bcrypt}),否则DelegatingPasswordEncoder无法识别 - 配置时至少注册两个编码器:
bcrypt(新默认)和你原来的旧编码器(如sha256或自定义LegacyPasswordEncoder) - 在
UserDetailsService.loadUserByUsername()返回的UserDetails中,确保getPassword()返回的是带前缀的完整字符串 - 登录成功后,在
AuthenticationSuccessHandler或自定义DaoAuthenticationProvider子类中检查:若原密码以{sha256}开头,就调用新BCryptPasswordEncoder.encode()生成新哈希,存回 DB 并清除旧前缀
容易被忽略的细节
最常翻车的地方不在配置,而在数据状态一致性:
- 数据库字段长度必须足够存新哈希(
BCrypt输出约 60 字符,SCrypt可能超 255,别用VARCHAR(50)) - 如果旧密码是
MD5或SHA-1这类无盐哈希,DelegatingPasswordEncoder无法直接支持,得自己写个LegacyMd5PasswordEncoder实现matches(),且务必加盐逻辑兼容原有规则 -
PasswordEncoderFactories.createDelegatingPasswordEncoder()返回的实例默认只含bcrypt/pbkdf2/scrypt/sha256,不含你旧系统用的私有算法,必须手写注册


















