spring-security (2) java config loading mechanism - @EnableGlobalAuthentication

Foreword
  In the previous article, through the discussion of the configuration class WebSecurityConfiguration in the EnableWebSecurity annotation, we know how to load various filters and the AccessDecisionManager for authentication, but how to load the AuthenticationManager for authentication. It has not been explained yet. In this article, we will focus on analyzing how various AuthenticationManagers are loaded.
  From the previous article, we know that the EnableWebSecurity annotation removes the introduction of the WebSecurityConfiguration configuration class, and also introduces the EnableGlobalAuthentication annotation. The secret related to the authentication mechanism is in this annotation. Let's focus on how this annotation is done.
Environment :
  spring -boot version: 1.5.4.RELEASE
1..@EnableGlobalAuthentication
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

This annotation mainly introduces the AuthenticationConfiguration configuration class
2. AuthenticationConfiguration class
...
@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(
			ObjectPostProcessor<Object> objectPostProcessor) {
		return new AuthenticationManagerBuilder(objectPostProcessor);
	}

	@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);
	}

	public AuthenticationManager getAuthenticationManager() throws Exception {
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
		AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
				this.objectPostProcessor);
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}

		for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
			authBuilder.apply(config);
		}

		authenticationManager = authBuilder.build();

		if (authenticationManager == null) {
			authenticationManager = getAuthenticationManagerBean();
		}

		this.authenticationManagerInitialized = true;
		return authenticationManager;
	}

	@Autowired(required = false)
	public void setGlobalAuthenticationConfigurers(
			List<GlobalAuthenticationConfigurerAdapter> configurers) throws Exception {
		Collections.sort(configurers, AnnotationAwareOrderComparator.INSTANCE);
		this.globalAuthConfigurers = configurers;
	}
...

The most important part of this configuration class is the above part, which mainly does the following four things
  • Register a bean of type AuthenticationManagerBuilder
  • Three beans that implement GlobalAuthenticationConfigurerAdapter are registered: GlobalAuthenticationConfigurerAdapter, InitializeUserDetailsBeanManagerConfigurer, InitializeAuthenticationProviderBeanManagerConfigurer
  • A method getAuthenticationManager() is defined to obtain AuthenticationManager. How to call this method will be described below
  • Through the method of setGlobalAuthenticationConfigurers, the class that implements the abstract class of GlobalAuthenticationConfigurerAdapter is injected into the current class, which are the three beans we mentioned above. In addition to the above three classes, there are also
  • The other two beans are also injected into
    BootGlobalAuthenticationConfigurationAdapter and SpringBootAuthenticationConfigurerAdapter
    . Let's talk about how these two beans are created.
    First , configure springboot startup time in META-INF/spring.factories of spring-boot-autoconfigure-{version}.jar Automatically loaded classes
    Among them are the following four related to spring security:
    org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration, \
    org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration, \
    org.springframework.boot.autoconfigure.security. FallbackWebSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\

    The class related to our two beans is SecurityAutoConfiguration, which introduces
    SpringBootWebSecurityConfiguration
    BootGlobalAuthenticationConfiguration
    AuthenticationManagerConfiguration These three configuration classes

    SpringBootWebSecurityConfiguration The function of this class is to ensure that the basic spring security functions can be used as long as we introduce the spring security-related jars, because we introduce EnableWebSecurity, this annotation introduces WebSecurityConfiguration, so this configuration class is It doesn't work.
    In the BootGlobalAuthenticationConfiguration configuration class, you can see that the bean we are looking for is defined -BootGlobalAuthenticationConfiguration This bean definition is also static
    AuthenticationManagerConfiguration This configuration class defines two beans, one of which is the SpringBootAuthenticationConfigurerAdapter we are looking for. The definition of the bean is also static. The
    other is the very important definition of the AuthenticationManager bean and it is
    @Primary. The method of AuthenticationConfiguration.getAuthenticationManager() mentioned above is called, but because we have already called it when configuring httpSecurity in the WebSecurityConfigurerAdapter class Once
    so this call to this method will return directly.


For the call of the getAuthenticationManager() method, we need to revisit the getHttp() method of the WebSecurityConfigurerAdapter class
3. WebSecurityConfigurerAdapter
protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
......

protected AuthenticationManager authenticationManager() throws Exception {
		if (!authenticationManagerInitialized) {
			configure(localConfigureAuthenticationBldr);
			if (disableLocalConfigureAuthenticationBldr) {
				authenticationManager = authenticationConfiguration
						.getAuthenticationManager();
			}
			else {
				authenticationManager = localConfigureAuthenticationBldr.build();
			}
			authenticationManagerInitialized = true;
		}
		return authenticationManager;
	}

The calling process is the above two pieces of code. By default, disableLocalConfigureAuthenticationBldr is true, so authenticationConfiguration.getAuthenticationManager() is finally called; that is, the method mentioned above.
In the getAuthenticationManager() method, the build method of the AuthenticationManagerBuilder class is mainly called to obtain an AuthenticationManager. The AuthenticationManagerBuilder class, like the httpSecurity and webSecurity classes, inherits from AbstractConfiguredSecurityBuilder, so the build process is also the same as init->configure->performBuild. In During this process, the configure methods of the injected GlobalAuthenticationConfigurerAdapter classes will be called in turn.
The following is a class of InitializeUserDetailsBeanManagerConfigurer to illustrate
4.InitializeUserDetailsBeanManagerConfigurer
....
	@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;
			}

			PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);

			DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
			provider.setUserDetailsService(userDetailsService);
			if (passwordEncoder != null) {
				provider.setPasswordEncoder(passwordEncoder);
			}

			auth.authenticationProvider(provider);
		}
....

This class adds a new InitializeUserDetailsManagerConfigurer class to AuthenticationManagerBuilder during init. You can see that a DaoAuthenticationProvider is created for us in this class, and the created DaoAuthenticationProvider is put into AuthenticationManagerBuilder, which is defined in applicationcontext during the creation process. The UserDetailsService and PasswordEncoder classes that have been passed, this is the reason why we can implement our own authentication logic by defining a UserDetailsService as we have seen in many examples.
The implementation logic of other Configures is similar, but the things they do are different, so they will not be expanded one by one. Let's mainly look at the performBuild() method of
AuthenticationManagerBuilder 5. AuthenticationManagerBuilder
@Override
	protected ProviderManager performBuild() throws Exception {
		if (!isConfigured()) {
			logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
			return null;
		}
		ProviderManager providerManager = new ProviderManager(authenticationProviders,
				parentAuthenticationManager);
		if (eraseCredentials != null) {
			providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
		}
		if (eventPublisher != null) {
			providerManager.setAuthenticationEventPublisher(eventPublisher);
		}
		providerManager = postProcess(providerManager);
		return providerManager;
	}

In this method, a ProviderManager class is created with the authenticationProviders we used before, and a parentAuthenticationManager parameter is also set. At this stage, the value of the parentAuthenticationManager property is null. Let's talk about when this field is used.
This setting is still in the getHttp() method of the WebSecurityConfigurerAdapter class.
After calling the authenticationManager() method in this method, the created AuthenticationManager is set to the parentAuthenticationManager property of the authenticationBuilder, that is, the creation process we just analyzed just created a parentAuthenticationManager , when will you continue to use the authenticationBuilder to create a real authenticationManager? The answer is in the HttpSecurity class
6. HttpSecurity
@Override
	protected void beforeConfigure() throws Exception {
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
	}

Through the previous article, we know that in the build method of HttpSecurity, the beforeConfigure method of this class will be called first. At this time, we will register the various authentication methods defined in WebSecurityConfigurerAdapter. If we want to use the INMEMORY authentication method, we can configure it like this
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void auth(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("chengf").password("sso").authorities("ROLE_USER", "ROLE_ADMIN").and()
				.withUser("user").password("password").authorities("ROLE_USER");
	}
}

Or when using cas authentication
@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable().authorizeRequests().anyRequest().hasRole("USER").and()
				.exceptionHandling().authenticationEntryPoint(casEntryPoint()).and()
				.addFilter(casFilter())
				.authenticationProvider(casAuthenticationProvider());
	}

Therefore, under normal circumstances, the ProviderManager will have a parent attribute, which is why the authentication method of the ProviderManager class will judge that if the current ProviderManager cannot be successfully authenticated, the parent will be used for authentication.

So far, the main component loading logic in spring-security java config has been finished.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326117652&siteId=291194637