最近,我在一个使用自定义PasswordEncoder
的项目中工作,需要将其迁移到bcrypt 。 当前的密码存储为hash
,这意味着不可能将其还原为原始的String
–至少不是简单的方法。
这里的挑战是如何支持两种实现,旧的哈希解决方案以及新的bcrypt
实现。 经过一些研究,我可以找到Spring Security 5的 DelegatingPasswordEncoder
。
认识DelegatingPasswordEncoder
DelegatingPasswordEncoder
类可以支持基于前缀的多个password encoders
。 密码存储如下:
{bcrypt}$2a$10$vCXMWCn7fDZWOcLnIEhmK.74dvK1Eh8ae2WrWlhr2ETPLoxQctN4.
{noop}plaintextpassword
Spring Security 5带来了方便的PasswordEncoderFactories
类,当前该类支持以下编码器:
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
现在,无需声明单个PasswordEncoder
我们可以使用PasswordEncoderFactories
,例如以下代码片段:
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
添加自定义编码器
现在,回到我最初的问题,由于遗留原因,有一个本地的password encoding
解决方案,并且方便使用的PasswordEncoderFactories
对它一无所知,以解决我已经创建了一个类似于PasswordEncoderFactories
的类并添加了所有已构建的类的问题。 -in编码器和我的自定义编码器,这是一个示例实现:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import java.util.HashMap;
import java.util.Map;
class DefaultPasswordEncoderFactories {
@SuppressWarnings("deprecation")
static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("custom", new CustomPasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
然后,我使用DefaultPasswordEncoderFactories
声明了@Bean
。
第一次运行后,我意识到另一个问题,我将必须运行一个SQL
脚本来更新所有现有的密码,并添加{custom}
前缀,以便框架可以正确地将前缀与正确的PasswordEncoder
绑定,不要误解我的意思。很好的解决方案,但是我真的不想弄乱数据库中的现有密码,幸运的是, DelegatingPasswordEncoder
类允许我们设置默认的 PasswordEncoder
,这意味着只要框架尝试在存储的密码中找不到前缀,它都会退回default
以尝试对其进行解码。
然后,我将实现更改为以下内容:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import java.util.HashMap;
import java.util.Map;
class DefaultPasswordEncoderFactories {
@SuppressWarnings("deprecation")
static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new CustomPasswordEncoder());
return delegatingPasswordEncoder;
}
}
@Bean
声明现在是:
@Bean
public PasswordEncoder passwordEncoder() {
return DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder();
}
结论
迁移密码编码器是一个现实生活中的问题, Spring Security 5通过一次支持多个PasswordEncoder
提供了一种便捷的方式来轻松处理它。
脚注
- 本教程使用的代码可以在GitHub上找到 。
- DelegatingPasswordEncoder – Spring Docs
翻译自: https://www.javacodegeeks.com/2019/03/password-encoder-migration-spring-security-5.html