Spring Boot 1.5.12升级至2.1.9:ShaPasswordEncoder适配问题求助
嗨,我之前升级Spring Boot的时候也碰到过这个问题,刚好可以给你分享下解决思路和具体步骤:
首先得明确,Spring Boot 2.x对应的Spring Security 5.x重构了密码加密体系,原来的org.springframework.security.authentication.encoding.ShaPasswordEncoder已经被移除了,取而代之的是org.springframework.security.crypto.password.PasswordEncoder接口为核心的新体系,这个接口里的matches(CharSequence rawPassword, String encodedPassword)方法就是替代原来isPasswordValid的核心方法。
具体解决步骤
1. 替换编码器并兼容原有加盐逻辑
原来的ShaPasswordEncoder会自动把密码和盐拼接成密码{盐}的格式再哈希,现在我们可以用MessageDigestPasswordEncoder来兼容这个逻辑:
第一步:配置编码器Bean
在你的配置类里定义对应的编码器(这里以SHA-1为例,如果你原来用的是SHA-256就改成"sha-256"):
@Bean public MessageDigestPasswordEncoder passwordEncoder() { MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("sha-1"); encoder.setEncodeHashAsBase64(false); // 这里要和原来的ShaPasswordEncoder保持一致,默认是Hex编码而非Base64 return encoder; }
第二步:修改原有代码
原来的encode和isPasswordValid逻辑需要调整为适配新编码器的写法:
@Autowired private MessageDigestPasswordEncoder encoder; // 替换原来的encode逻辑 String rawPassword = (String) auth.getCredentials(); String saltedPassword = rawPassword + "{" + salt + "}"; // 模拟原来的加盐拼接逻辑 String passwordHash = encoder.encode(saltedPassword); // 替换原来的isPasswordValid验证逻辑 if (cachedAuth != null) { String cachedEncodedPassword = (String) cachedAuth.getCredentials(); String rawPasswordToCheck = (String) auth.getCredentials(); String saltedPasswordToCheck = rawPasswordToCheck + "{" + salt + "}"; boolean isValid = encoder.matches(saltedPasswordToCheck, cachedEncodedPassword); if (isValid) { return cachedAuth; } }
2. 更优雅的兼容:自定义编码器(减少代码改动)
如果不想在业务代码里到处改拼接逻辑,可以封装一个自定义编码器模拟原来的ShaPasswordEncoder行为,这样原有代码改动极小:
public class LegacyShaPasswordEncoder implements PasswordEncoder { private final MessageDigestPasswordEncoder delegate; public LegacyShaPasswordEncoder(String algorithm) { this.delegate = new MessageDigestPasswordEncoder(algorithm); this.delegate.setEncodeHashAsBase64(false); // 匹配原编码器默认配置 } // 模拟原来的encode方法(带盐) public String encode(String rawPassword, Object salt) { String saltedPass = rawPassword + "{" + salt + "}"; return delegate.encode(saltedPass); } // 模拟原来的isPasswordValid方法 public boolean isPasswordValid(String encodedPassword, String rawPassword, Object salt) { String saltedPass = rawPassword + "{" + salt + "}"; return delegate.matches(saltedPass, encodedPassword); } // 实现PasswordEncoder接口的默认方法(如果不需要可以抛出异常,或按需实现) @Override public String encode(CharSequence rawPassword) { throw new UnsupportedOperationException("请使用带盐的encode方法"); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { throw new UnsupportedOperationException("请使用带盐的isPasswordValid方法"); } }
然后配置这个自定义编码器的Bean:
@Bean public LegacyShaPasswordEncoder legacyShaPasswordEncoder() { return new LegacyShaPasswordEncoder("sha-1"); // 对应你原来的SHA算法 }
这样原有代码只需要把注入的编码器类型改成LegacyShaPasswordEncoder,再给encode方法加上盐参数即可:
@Autowired private LegacyShaPasswordEncoder encoder; [...] String passwordHash = encoder.encode((String)auth.getCredentials(), salt); // 补充salt参数 if (cachedAuth!=null && encoder.isPasswordValid((String) cachedAuth.getCredentials(), (String)auth.getCredentials(), salt)){ return cachedAuth; }
3. 推荐:逐步迁移到更安全的密码算法
Spring Security 5.x强烈推荐使用BCrypt、Argon2这类更安全的自适应哈希算法,你可以用DelegatingPasswordEncoder同时支持新老算法,实现平滑迁移:
@Bean public PasswordEncoder passwordEncoder() { Map<String, PasswordEncoder> encoders = new HashMap<>(); // 兼容老用户的SHA编码器 encoders.put("sha-1", new LegacyShaPasswordEncoder("sha-1")); // 新用户使用BCrypt encoders.put("bcrypt", new BCryptPasswordEncoder()); DelegatingPasswordEncoder delegatingEncoder = new DelegatingPasswordEncoder("bcrypt", encoders); // 没有前缀的老密码用SHA编码器验证 delegatingEncoder.setDefaultPasswordEncoderForMatches(new LegacyShaPasswordEncoder("sha-1")); return delegatingEncoder; }
这样新生成的密码会带上{bcrypt}前缀,老密码无前缀仍能正常验证,后续可以逐步把老用户的密码迁移到BCrypt。
内容的提问来源于stack exchange,提问作者Rajesh Rajoji

