上一篇博客中已经了解了,认证的核心是各种AuthenticationProvider,这篇博客就来了解一下其中使用的最多的一个DaoAuthenticationProvider。
在此之前,先来了解一下三个类。UserDetails、UserDetailsService以及PasswordEncoder。
通俗的说,UserDetails是用户信息的实体类,UserDetailsService自定义实现,根据用户名密码加载UserDetails,PasswordEncoder就是密码的加密类。
看图,DaoAuthenticationProvider通过UserDetailsService以及PasswordEncoder,将用户名密码替换成UserDetails和Authorities。
接着看一看这个DaoAuthenticationProvider是从哪里配置来的,这个得从WebSecurityConfigurerAdapter说起。
在WebSecurityConfigurerAdapter中,以下代码之前都看过,AuthenticationManager 的由来也都说明过,其中有一个parent被所有的子AuthenticationManager共享,而这个DaoAuthenticationProvider就是包括在parent中。
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
//获取AuthenticationEventPublisher
AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
//配置parent的AuthenticationManager,可覆盖实现自定义方法
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<?>, Object> sharedObjects = createSharedObjects();
//新建HttpSecurity,并构建一个默认的HttpSecurity
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
//spi 加载 AbstractHttpConfigurer的实现类,加入HttpSecurity中
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
//配置HttpSecurity,可自定义实现
configure(http);
return http;
}
protected AuthenticationManager authenticationManager() throws Exception {
//如果没有初始化过,执行方法
if (!authenticationManagerInitialized) {
//configure(AuthenticationManagerBuilder auth)
//交给子类自定义
configure(localConfigureAuthenticationBldr);
//如果没有自定义过
if (disableLocalConfigureAuthenticationBldr) {
//使用默认的
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
//否则使用自定义过的进行构建
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
以上代码可以看出,这个parent如果不自定义的话,会使用默认的authenticationConfiguration,而这个如下是spring注入进来的
@Autowired
public void setAuthenticationConfiguration(
AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
接下来再看AuthenticationConfiguration 是怎么加到spring容器中的。
还是@EnableWebSecurity注解,注解了一个@EnableGlobalAuthentication,这个注解import了一个AuthenticationConfiguration。
//默认的情况获取AuthenticationManager
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
//放入三个configure,都在当前类中生成,GlobalAuthenticationConfigurerAdapter
// InitializeUserDetailsBeanManagerConfigurer、InitializeAuthenticationProviderBeanManagerConfigurer
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
而在这个AuthenticationConfiguration中,会生成如上的AuthenticationManager,这个就是parent。
而在这个AuthenticationManager种,设置了多个globalAuthConfigurers
@Bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
ApplicationContext context) {
return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
可以看到,当前类中有这么三个Configurer,其中InitializeUserDetailsBeanManagerConfigurer中会生成DaoAuthenticationProvider,要注意的是,这里并不是在这个类中直接生成的,而是如下,先注入了一个InitializeUserDetailsManagerConfigurer,在InitializeUserDetailsManagerConfigurer中创建的。
Configurer的逻辑前几篇博客已经说明过了
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
//从spring中获取UserDetailsService,如果没有,直接返回
//所以要执行下面的代码,必须要有UserDetailsService
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
//从spring中获取PasswordEncoder
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
//构建DaoAuthenticationProvider
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
//将provider放入AuthenticationManagerBuilder中
auth.authenticationProvider(provider);
}
到此,DaoAuthenticationProvider的由来就已经清晰了。
再看看逻辑,如下代码,逻辑和之前描述一致。
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//调用UserDetailsService的loadUserByUsername方法,方法需要自定义
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
结束!