设计模式之工厂模式可以参考下面两篇文章:
[读书| 设计模式之禅 - 工厂方法模式][_ _ -]
[王争| 设计模式之美 - 工厂模式:我为什么说没事不要随便用工厂模式创建对象?][_ _ - 1]
1. PasswordEncoderFactories 演进分析
我们根据代表不同密码加密算法的encodeId(比如:bcrypt,ldap,pbkdf2,noop
等),来选择不同的加密算法(比如:BCryptPasswordEncode,rLdapShaPasswordEncoder,NoOpPasswordEncoder等)对密码进行加密,如果我们使用传统的方式如何写:
public class PasswordEncoderResource {
public PasswordEncoder load(){
String encodeId = getEncodeId();
if("bcrypt".equalsIgnoreCase(encodeId)){
return new BCryptPasswordEncoder();
}else if("ldap".equalsIgnoreCase(encodeId)){
return new LdapShaPasswordEncoder();
}else if("noop".equalsIgnoreCase(encodeId)){
return NoOpPasswordEncoder.getInstance();
}else if("pbkdf2".equalsIgnoreCase(encodeId)){
return new Pbkdf2PasswordEncoder();
}else if("scrypt".equalsIgnoreCase(encodeId)){
return new SCryptPasswordEncoder();
}else if("SHA-1".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-1");
}else if("SHA-256".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-256");
}else if("sha256".equalsIgnoreCase(encodeId)){
return new StandardPasswordEncoder();
}else if("argon2".equalsIgnoreCase(encodeId)){
return new Argon2PasswordEncoder();
}
return new BCryptPasswordEncoder();
}
private String getEncodeId() {
return "bcrypt";
}
}
为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数,我们可以将代码中涉及 PasswordEncoder 创建的部分逻辑剥离出来,抽象成 createPasswordEncoder(String encodeId) 函数。重构之后的代码如下所示:
public class PasswordEncoderResource {
public PasswordEncoder load(){
String encodeId = getEncodeId();
return createPasswordEncoder(encodeId);
}
private PasswordEncoder createPasswordEncoder(String encodeId) {
if("bcrypt".equalsIgnoreCase(encodeId)){
return new BCryptPasswordEncoder();
}else if("ldap".equalsIgnoreCase(encodeId)){
return new LdapShaPasswordEncoder();
}else if("noop".equalsIgnoreCase(encodeId)){
return NoOpPasswordEncoder.getInstance();
}else if("pbkdf2".equalsIgnoreCase(encodeId)){
return new Pbkdf2PasswordEncoder();
}else if("scrypt".equalsIgnoreCase(encodeId)){
return new SCryptPasswordEncoder();
}else if("SHA-1".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-1");
}else if("SHA-256".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-256");
}else if("sha256".equalsIgnoreCase(encodeId)){
return new StandardPasswordEncoder();
}else if("argon2".equalsIgnoreCase(encodeId)){
return new Argon2PasswordEncoder();
}
return new BCryptPasswordEncoder();
}
private String getEncodeId() {
return "bcrypt";
}
}
为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createPasswordEncoder(String encodeId) 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是简单工厂模式类:
public class PasswordEncoderResource {
public PasswordEncoder load(){
String encodeId = getEncodeId();
return PasswordEncoderFactory.createPasswordEncoder(encodeId);
}
private String getEncodeId() {
return "bcrypt";
}
}
public class PasswordEncoderFactory {
public static PasswordEncoder createPasswordEncoder(String encodeId) {
if("bcrypt".equalsIgnoreCase(encodeId)){
return new BCryptPasswordEncoder();
}else if("ldap".equalsIgnoreCase(encodeId)){
return new LdapShaPasswordEncoder();
}else if("noop".equalsIgnoreCase(encodeId)){
return NoOpPasswordEncoder.getInstance();
}else if("pbkdf2".equalsIgnoreCase(encodeId)){
return new Pbkdf2PasswordEncoder();
}else if("scrypt".equalsIgnoreCase(encodeId)){
return new SCryptPasswordEncoder();
}else if("SHA-1".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-1");
}else if("SHA-256".equalsIgnoreCase(encodeId)){
return new MessageDigestPasswordEncoder("SHA-256");
}else if("sha256".equalsIgnoreCase(encodeId)){
return new StandardPasswordEncoder();
}else if("argon2".equalsIgnoreCase(encodeId)){
return new Argon2PasswordEncoder();
}
return new BCryptPasswordEncoder();
}
}
我们每次调用 PasswordEncoderFactory#createPasswordEncoder 的时候,都要创建一个新的 PasswordEncoder,实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 PasswordEncoder 事先创建好缓存起来。当调用 PasswordEncoderFactory#createPasswordEncoder 函数的时候,我们从缓存中取出 PasswordEncoder 对象直接使用。
通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到Map容器中,以方便下次调用。
public class PasswordEncoderFactory {
public static final Map<String, PasswordEncoder> encoders = new HashMap<>();
static {
encoders.put("bcrypt", 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("argon2", new Argon2PasswordEncoder());
}
public static PasswordEncoder createPasswordEncoder(String encodeId) {
if(!StringUtils.hasText(encodeId)){
return null;
}
return encoders.get(encodeId);
}
}
2. PasswordEncoderFactories 源码分析
1. PasswordEncoderFactories
public class PasswordEncoderFactories {
/**
* 创建DelegatingPasswordEncoder的实例
*/
@SuppressWarnings("deprecation")
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());
encoders.put("argon2", new Argon2PasswordEncoder());
// DelegatingPasswordEncoder默认使用的是BCryptPasswordEncoder加密
return new DelegatingPasswordEncoder(encodingId, encoders);
}
private PasswordEncoderFactories() {
}
}
DelegatingPasswordEncoder 是一个代理类,主要用来代理上面介绍的不同加密方式,它允许系统中存在不同的加密方案,很方便的完成对加密方案的升级。如果是通过PasswordEncoderFactories#createDelegatingPasswordEncoder方法创建的DelegatingPasswordEncoder实例时,默认其实使用的还是BCryptPasswordEncoder
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.authenticated();
}
@Bean
public UserDetailsService users() {
// {MD5}value必须大写,value值必须是32位小写
// admin
UserDetails admin = User.builder()
//.passwordEncoder(encoder::encode)
.username("admin").password(
"{MD5}e10adc3949ba59abbe56e057f20f883e"
).roles("admin").build();
// lisi
UserDetails hengboy = User.builder()
.username("lisi")
.password("{bcrypt}$2a$10$iMz8sMVMiOgRgXRuREF/f.ChT/rpu2ZtitfkT5CkDbZpZlFhLxO3y")
.roles("admin")
.build();
// zhangsan
UserDetails yuqiyu = User.builder().username("zhangsan")
//.password("{noop}123456")
.password("{pbkdf2}cc409867e39f011f6332bbb6634f58e98d07be7fceefb4cc27e62501594d6ed0b271a25fd9f7fc2e")
.roles("user").build();
return new InMemoryUserDetailsManager(admin, zhangsan, lisi);
}
}
2. 客户端 WebSecurityConfigurerAdapter
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
static class LazyPasswordEncoder implements PasswordEncoder {
private PasswordEncoder passwordEncoder;
// ...
private PasswordEncoder getPasswordEncoder() {
if (this.passwordEncoder != null) {
return this.passwordEncoder;
}
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
if (passwordEncoder == null) {
passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
this.passwordEncoder = passwordEncoder;
return passwordEncoder;
}
// ....
}
}
3. DelegatingPasswordEncoder
public class DelegatingPasswordEncoder implements PasswordEncoder {
private static final String PREFIX = "{";
private static final String SUFFIX = "}";
private final String idForEncode;
private final PasswordEncoder passwordEncoderForEncode;
private final Map<String, PasswordEncoder> idToPasswordEncoder;
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
if (idForEncode == null) {
throw new IllegalArgumentException("idForEncode cannot be null");
}
if (!idToPasswordEncoder.containsKey(idForEncode)) {
throw new IllegalArgumentException(
"idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
}
for (String id : idToPasswordEncoder.keySet()) {
if (id == null) {
continue;
}
if (id.contains(PREFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
}
if (id.contains(SUFFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
}
}
this.idForEncode = idForEncode;
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
}
public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {
if (defaultPasswordEncoderForMatches == null) {
throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
}
this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
}
@Override
public String encode(CharSequence rawPassword) {
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
String id = extractId(prefixEncodedPassword);
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
}
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
private String extractId(String prefixEncodedPassword) {
if (prefixEncodedPassword == null) {
return null;
}
int start = prefixEncodedPassword.indexOf(PREFIX);
if (start != 0) {
return null;
}
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
if (end < 0) {
return null;
}
return prefixEncodedPassword.substring(start + 1, end);
}
@Override
public boolean upgradeEncoding(String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
if (!this.idForEncode.equalsIgnoreCase(id)) {
return true;
}
else {
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
}
}
private String extractEncodedPassword(String prefixEncodedPassword) {
int start = prefixEncodedPassword.indexOf(SUFFIX);
return prefixEncodedPassword.substring(start + 1);
}
/**
* Default {@link PasswordEncoder} that throws an exception telling that a suitable
* {@link PasswordEncoder} for the id could not be found.
*/
private class UnmappedIdPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
throw new UnsupportedOperationException("encode is not supported");
}
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
}
}
}