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 默认会立即清除明文密码,所以需要特殊配置。

迁移步骤:

  1. 监听 AuthenticationSuccessEvent 事件
  2. 从认证对象中提取明文密码
  3. 用新算法重新编码
  4. 更新用户密码并清除临时凭证

事件监听器实现:

@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 仓库


原始标题:New Password Storage in Spring Security 5