是谁的 PasswordEncoder
根据前面 3.1.2 小节的介绍,大家知道 PasswordEncoder 做密码校验主要是在 DaoAuthenticationProvider 中完成的:根据 3.1.3 小节的介绍,DaoAuthenticationProvider 是被某一个 ProviderManager 管理的;而根据 4.1 节的介绍,AuthenticationManager(即 ProviderManager)实例有全局和局部之分,那么如果开发者配置了 PasswordEncoder 实例,是在全局的 AuthenticationManager 中使用,还是在局部的 AuthenticationManager 中使用呢?
我们先来看 DaoAuthenticationProvider 中的构造方法:
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
可以看到,当系统创建一个 DaoAuthenticationProvider 实例的时候,就会自动调用 setPasswordEncoder 方法来指定一个默认的 PasswordEncoder,默认的 PasswordEncoder 实例就是 DelegatingPasswordEncoder。
在全局的 AuthenticationManager 创建过程中,在 InitializeUserDetailsManagerConfigurer#configure 方法中,有如下一段代码:
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
首先调用 getBeanOrNull 方法,从 Spring 容器中获取一个 PasswordEncoder 实例;然后创建一个 DaoAuthenticationProvider 实例,如果 passwordEncoder 不为 null,就设置给 provider 实例。从这段代码中可以看到,在上一小节中我们注册到 Spring 容器的 PasswordEncoder 实例,可以在这里被获取到并设置给 provider。如果我们没有向 Spring 容器中注册 PasswordEncoder 实例,则 provider 中使用默认的 DelegatingPasswordEncoder。
根据前面 4.1.5 小节的介绍可知,全局 AuthenticationManager 也有可能是通过 WebSecurityConfigurerAdapter 中的 localConfigureAuthenticationBldr 变量来构建的,localConfigureAuthenticationBldr 变量在构建 AuthenticationManager 实例时,使用的是 LazyPasswordEncoder,就是一个懒加载的 PasswordEncoder 实例,代码如下:
static class LazyPasswordEncoder implements PasswordEncoder {
private ApplicationContext applicationContext;
private PasswordEncoder passwordEncoder;
LazyPasswordEncoder(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public String encode(CharSequence rawPassword) {
return getPasswordEncoder().encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword,
String encodedPassword) {
return getPasswordEncoder().matches(rawPassword, encodedPassword);
}
@Override
public boolean upgradeEncoding(String encodedPassword) {
return getPasswordEncoder().upgradeEncoding(encodedPassword);
}
private PasswordEncoder getPasswordEncoder() {
if (this.passwordEncoder != null) {
return this.passwordEncoder;
}
PasswordEncoder passwordEncoder = getBeanOrNull(this.applicationContext, PasswordEncoder.class);
if (passwordEncoder == null) {
passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
this.passwordEncoder = passwordEncoder;
return passwordEncoder;
}
@Override
public String toString() {
return getPasswordEncoder().toString();
}
}
可以看到,在 LazyPasswordEncoder 中,使用 getPasswordEncoder 方法去获取一个 PasswordEncoder 实例,具体的获取过程就是去 Spring 容器中查找,找到了就直接使用,没找到的话,则调用 PasswordEncoderFactories.createDelegatingPasswordEncoder() 方法生成默认的 DelegatingPasswordEncoder。
在 WebSecurityConfigurerAdapter 中,用来构建局部 AuthenticationManager 实例的 authenticationBuilder 变量也用的是 LazyPasswordEncoder,这里不再赞述。
经过上面的分析可知,如果开发者向 Spring 容器中注册了一个 PasswordEncoder 实例,那么无论是全局的 AuthenticationManager 还是局部的 AuthenticationManager,都将使用该 PasswordEncoder 实例;如果开发者没有提供任何 PasswordEncoder 实例,那么无论是全局的 AuthenticationManager 还是局部的 AuthenticationManager,都将使用 DelegatingPasswordEncoder 实例。