Spring Security Core 3.2.4至5.8.6迁移:密码编码问题咨询
从Spring Security Core 3.2.4.RELEASE迁移至5.8.6时,原有的ShaPasswordEncoder已被弃用,使用DelegatingPasswordEncoder无法直接适配原有Salt逻辑,且StandardPasswordEncoder也已弃用,以下是对应解决方案:
一、适配原有Salt逻辑的迁移代码
原有代码包含三种密码编码逻辑,需实现兼容Spring Security 5.x的编码逻辑,同时可整合到DelegatingPasswordEncoder中支持多编码格式。
1. 兼容原有编码逻辑的自定义实现
Spring Security 5.x的PasswordEncoder接口默认不直接接收Salt参数,可自定义工具类或实现接口适配原有逻辑:
import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.password.PasswordEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class LegacyShaPasswordEncoder implements PasswordEncoder { private final String algorithm; // 对应原有无参数ShaPasswordEncoder(SHA-1) public LegacyShaPasswordEncoder() { this("SHA"); } // 对应原有ShaPasswordEncoder(256)(SHA-256) public LegacyShaPasswordEncoder(int strength) { this(strength == 256 ? "SHA-256" : "SHA"); } private LegacyShaPasswordEncoder(String algorithm) { this.algorithm = algorithm; } // 适配原有encodePassword方法:先无Salt编码一次,再用Salt编码一次 public String encodeTwice(String rawPassword, String salt) { String firstEncode = encode(rawPassword, null); return encode(firstEncode, salt); } // 适配原有changeEncodedPassword/encodSH2Password方法:直接用Salt编码 public String encode(String rawPassword, String salt) { try { MessageDigest digest = MessageDigest.getInstance(this.algorithm); if (salt != null) { digest.update(salt.getBytes()); } byte[] hashed = digest.digest(rawPassword.getBytes()); return new String(Hex.encode(hashed)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("密码编码失败", e); } } @Override public String encode(CharSequence rawPassword) { return encode(rawPassword.toString(), null); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { // 验证时需结合业务逻辑获取对应Salt,再调用encode方法对比结果 throw new UnsupportedOperationException("请结合业务获取Salt后调用对应encode方法完成匹配"); } }
2. 整合到DelegatingPasswordEncoder
若需同时支持新旧编码格式,可将自定义编码器加入DelegatingPasswordEncoder,实现新旧密码兼容:
import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.HashMap; import java.util.Map; public class PasswordEncoderFactory { public static PasswordEncoder createDelegatingPasswordEncoder() { // 新密码默认使用更安全的bcrypt String idForEncode = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); // 添加原有编码器适配旧数据 encoders.put("sha", new LegacyShaPasswordEncoder()); encoders.put("sha256", new LegacyShaPasswordEncoder(256)); // 添加现代安全编码器 encoders.put("bcrypt", new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder()); encoders.put("pbkdf2", org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()); DelegatingPasswordEncoder delegatingEncoder = new DelegatingPasswordEncoder(idForEncode, encoders); // 无前缀的旧密码默认用SHA编码器匹配 delegatingEncoder.setDefaultPasswordEncoderForMatches(new LegacyShaPasswordEncoder()); return delegatingEncoder; } }
二、StandardPasswordEncoder的替代方案
StandardPasswordEncoder已被弃用,官方推荐以下更安全的编码器:
BCryptPasswordEncoder:基于bcrypt哈希算法,自动生成随机Salt,安全性高,是当前主流选择:
PasswordEncoder bcryptEncoder = new BCryptPasswordEncoder(); String encodedPwd = bcryptEncoder.encode("rawPassword"); boolean isMatch = bcryptEncoder.matches("rawPassword", encodedPwd);Pbkdf2PasswordEncoder:基于PBKDF2算法,适合高安全性场景,Spring Security 5.8+提供默认配置:
PasswordEncoder pbkdf2Encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();SCryptPasswordEncoder:基于scrypt算法,内存消耗更高,能有效抵御暴力破解:
PasswordEncoder scryptEncoder = new org.springframework.security.crypto.scrypt.SCryptPasswordEncoder();
迁移注意事项
若需兼容原有StandardPasswordEncoder加密的数据,可暂时保留该编码器直至完成数据批量迁移,或编写转换逻辑将旧数据迁移至新编码格式。
内容的提问来源于stack exchange,提问作者Pranav Siddhant

