Voting portal
1. Introduction
Today, a student told me that there was a problem in the Day11 branch of Security Learning project. The verification code login was incompatible with other login, and there was a No Provider exception. And this? I did a quick run. It seems that I was careless, but I finally found the cause. The problem was the initialization of AuthenticationManager. After customizing a UseDetailService and AuthenticationProvider, the default initialization of AuthenticationManager is broken.
Although the flow of AuthenticationManager was analyzed in the Spring Security hands-on dry: graphical AuthenticationManager article, it was not deep enough to cause problems. We’re going to fix this hole today.
2. Initialize the AuthenticationManager
For the initialization of AuthenticationManager, see this article in the flow section, which has a flow chart. We mentioned in the flow chart of the AuthenticationManager default initialization is completed by AuthenticationConfiguration, but only one has brought, the details is not clear. Fix it now.
AuthenticationConfiguration
AuthenticationConfiguration initialize the AuthenticationManager core method is the following:
public AuthenticationManager getAuthenticationManager(a) throws Exception {
// Check whether AuthenticationManager is initialized first
if (this.authenticationManagerInitialized) {
// Return initialized if already initialized
return this.authenticationManager;
}
// Otherwise go to Spring IoC and get its build class
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
// If it is not the first time to build, it seems like every time to build through the Builder
if (this.buildingAuthenticationManager.getAndSet(true)) {
// Returns a delegated AuthenticationManager
return new AuthenticationManagerDelegator(authBuilder);
}
// If the Builder is built for the first time and the global authentication configuration is integrated into the Builder, then there is no need to integrate the global configuration
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
/ / build the AuthenticationManager
authenticationManager = authBuilder.build();
// If the build result is null
if (authenticationManager == null) {
// Try again to get the lazy-loaded AuthenticationManager Bean from Spring IoC
authenticationManager = getAuthenticationManagerBean();
}
// Change the initialization state
this.authenticationManagerInitialized = true;
return authenticationManager;
}
Copy the code
According to the comments above, the initialization process of AuthenticationManager is clear. But two questions arise, and I will discuss them in two more chapters.
AuthenticationManagerBuilder
The first question is how AuthenticationManagerBuilder into Spring IoC?
AuthenticationManagerBuilder injection process was also done in the middle of AuthenticationConfiguration, Injection is the inside of a static class DefaultPasswordEncoderAuthenticationManagerBuilder, This class and Spring Security master configuration class WebSecurityConfigurerAdapter an inner class of the same name, these two classes almost the same logic, nothing special. Which by the specific use WebSecurityConfigurerAdapter. DisableLocalConfigureAuthenticationBldr decision.
The ObjectPostProcessor
argument is going to do something about that in a moment.
GlobalAuthenticationConfigurerAdapter
Another problem is that GlobalAuthenticationConfigurerAdapter where you come from?
The method of AuthenticationConfiguration contains the following automatic injection GlobalAuthenticationConfigurerAdapter:
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers( List
configurers)
{
configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
Copy the code
The method sorts them by their respective Order. The significance of this sort is AuthenticationManagerBuilder will sort by constructing the AuthenticationManager in the implementation of the executed GlobalAuthenticationConfigurerAdapter configure Methods.
Global Authentication Configuration
First for EnableGlobalAuthenticationAutowiredConfigurer, it now in addition to print the initialization information no practical effect.
The authentication handler initializes the injection
The second InitializeAuthenticationProviderBeanManagerConfigurer, core method for the realization of them were values:
@Override
public void configure(AuthenticationManagerBuilder auth) {
//
// If an AuthenticationProvider has been injected or an AuthenticationManager has been propped
if (auth.isConfigured()) {
return;
}
// Try to get the AuthenticationProvider from Spring IoC
AuthenticationProvider authenticationProvider = getBeanOrNull(
AuthenticationProvider.class);
// If not, interrupt
if (authenticationProvider == null) {
return;
}
/ / get get configured into the AuthenticationManagerBuilder, will eventually configuration into the AuthenticationManager
auth.authenticationProvider(authenticationProvider);
}
Copy the code
The getBeanOrNull method here is wrong if you don’t look at it carefully. The core code is as follows:
String[] userDetailsBeanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
// Spring IoC cannot have more than one type related Bean at the same time or it cannot be injected
if(userDetailsBeanNames.length ! =1) {
return null;
}
Copy the code
If there are multiple AuthenticationProviders in the Spring IoC container, these AuthenticationProviders will not take effect.
The user Details manager initializes the injection
The third is InitializeUserDetailsBeanManagerConfigurer, priority is lower than the above. Its core methods are:
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
// Can not have more than one or interrupt
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
/ / start configuring common password authentication, DaoAuthenticationProvider
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if(passwordEncoder ! =null) {
provider.setPasswordEncoder(passwordEncoder);
}
if(passwordManager ! =null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
Copy the code
As InitializeAuthenticationProviderBeanManagerConfigurer process, but here is the main treatment of UserDetailsService, DaoAuthenticationProvider. When the execution to the above method, if the Spring IoC container in multiple UserDetailsService, then these UserDetailsService will not take effect, influence DaoAuthenticationProvider injection.
3. The truth is out
When I use the default configuration of Spring Security (note this premise), To the Spring IoC injected with multiple UserDetailsService DaoAuthenticationProvider results in no effect. That is to say, if you have multiple UserDetailsService in a configuration of Spring beans will influence the DaoAuthenticationProvider injection.
But what if I still need to inject multiple AuthenticationProviders?
First inject the AuthenticationProvider you need to configure into Spring IoC and write it in HttpSecurity like this:
protected void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
CaptchaAuthenticationProvider captchaAuthenticationProvider = context.getBean("captchaAuthenticationProvider", CaptchaAuthenticationProvider.class);
http.authenticationProvider(captchaAuthenticationProvider);
/ / to omit
}
Copy the code
There are several AuthenticationProviders and you configure them as above.
Generally, one UserDetailsService corresponds to one AuthenticationProvider.
4. To summarize
This article is very important for Spring Security configuration that requires multiple authentication methods. If you do not pay attention to the configuration, it is easy to cause No Provider… The exception. So it’s very necessary to learn.
Personal blog: felord.cn