参考文章:http://www.javaboy.org/2020/0618/passwordencoder.html
spring security一系列都是从这里学的 非常感谢作者
在security框架中 如果没有创建任何一个PasswordEncoder 则会使用默认的 ,如果在容器中有PasswordEncoder 则会使用容器中的,源码如下:
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
private final ApplicationContext context;
/**
* @param context
*/
InitializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
this.context = context;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
//从容器中获取 没有则会返回null
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
//Provider的构造方法中 设置了默认的
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
//如果不是null 则使用容器中获取的
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
/**
* @return a bean of the requested class if there's just a single registered
* component, null otherwise.
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
}
而默认的encoder是一个同时支持多种加密方式的代理对象 provider的构造方法如下:
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
里面的具体实现是
@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());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
可以看到 生成了一个DelegatingPasswordEncoder对象 使用的ID是bcrypt 也就代表了该encoder 加密时使用的是bcrypt,解密时可以根据密码所带前缀选择不同的解密方式,如果没有带加密前缀,则会使用默认的default方式。
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
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 + "\"");
}
}
可以看到,默认都会直接抛出异常。
前缀带密码是这样的:
{bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi
{MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2
{noop}123
所以如果需要同时支持多种解密方式,只需要将原本的map扩展一下,使其支持自己写的encoder,并且将默认encoder的设置成你想要的,作为没有前缀时的解密方式即可。
当然直接使用自己写的encoder 在其中加入各种判断也是可以的。看个人选择。
@Bean
public PasswordEncoder passwordEncoder() {
return MyPasswordEncoderFactories.createDelegatingPasswordEncoder();
}
class MyPasswordEncoderFactories {
@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("SM3",new MyPasswordEncoder());
DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
//指定没有前缀时使用的方式
delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new MyPasswordEncoder());
return delegatingPasswordEncoder;
}
}
这样就可以使用多种加密方式了。