1. 引言
Spring Security 最新版本带来了不少变化,其中密码编码机制的重构尤为显著。本文将深入探讨这些关键更新,并演示如何配置新的委托机制,以及如何在用户无感知的情况下平滑迁移现有密码编码方案。
2. Spring Security 5.x 的核心变化
Spring Security 团队已将 org.springframework.security.authentication.encoding
包下的 PasswordEncoder
接口标记为废弃。这一决定很合理——旧接口设计并未考虑随机盐值(random salt)的需求,因此在 5.x 版本中该接口被彻底移除。
密码处理机制也发生了根本性变革:
- ✅ 旧版本:整个应用只能使用单一密码编码算法(默认使用 SHA-256 的
StandardPasswordEncoder
) - ❌ 新版本:引入密码编码委托机制,支持为不同密码使用不同算法
Spring 通过密码哈希前缀识别算法,例如 bcrypt 编码的密码:
{bcrypt}$2b$12$FaLabMRystU4MLAasNOKb.HUElBAabuQdX59RWHq5X.9Ghm692NEi
注意开头的 {bcrypt}
前缀,这就是算法标识符。
3. 委托机制配置
无前缀密码的处理逻辑:
- 默认使用
StandardPasswordEncoder
(保持向后兼容) - 通过
PasswordEncoderFactories.createDelegatingPasswordEncoder()
创建委托编码器
当前支持的算法见 官方 JavaDoc。
自定义配置示例(支持 bcrypt/scrypt/SHA-256):
@Bean
public PasswordEncoder delegatingPasswordEncoder() {
PasswordEncoder defaultEncoder = new StandardPasswordEncoder();
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder(1, 1, 1, 1, 10));
DelegatingPasswordEncoder passworEncoder = new DelegatingPasswordEncoder(
"bcrypt", encoders);
passworEncoder.setDefaultPasswordEncoderForMatches(defaultEncoder);
return passworEncoder;
}
4. 密码编码算法迁移实战
核心思路:利用登录请求获取明文密码,在认证成功后重新编码。这里有个关键点——Spring Security 默认会立即清除明文密码,所以需要特殊配置。
迁移步骤:
- 监听
AuthenticationSuccessEvent
事件 - 从认证对象中提取明文密码
- 用新算法重新编码
- 更新用户密码并清除临时凭证
事件监听器实现:
@Bean
public ApplicationListener<AuthenticationSuccessEvent>
authenticationSuccessListener( PasswordEncoder encoder) {
return (AuthenticationSuccessEvent event) -> {
Authentication auth = event.getAuthentication();
if (auth instanceof UsernamePasswordAuthenticationToken
&& auth.getCredentials() != null) {
CharSequence clearTextPass = (CharSequence) auth.getCredentials();
String newPasswordHash = encoder.encode(clearTextPass);
// [...] 更新用户密码(示例:调用userService.updatePassword())
((UsernamePasswordAuthenticationToken) auth).eraseCredentials();
}
};
}
⚠️ 关键配置:必须禁用自动清除凭证机制
@Configuration
public class PasswordStorageWebSecurityConfigurer {
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.eraseCredentials(false) // 禁用自动清除
.userDetailsService(getUserDefaultDetailsService())
.passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
}
5. 总结
本文系统解析了 Spring Security 5.x 的密码存储新特性:
- 🔄 委托编码机制支持多算法并存
- 🛠️ 通过前缀标识实现算法自动识别
- 🚀 利用认证事件实现无感迁移
这种设计既保证了安全性(支持强加密算法),又实现了平滑升级(用户无需重置密码)。实际项目中,建议优先使用 bcrypt 或 scrypt 等现代算法,并通过本文方案逐步淘汰旧算法。
完整代码示例见 GitHub 仓库。