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