spring-security(二)java config加载机制-@EnableGlobalAuthentication

前言
  在上一篇文章中通过对EnableWebSecurity注解中的配置类WebSecurityConfiguration的探讨,我们知道了filter各种各样的filter以及鉴权用的AccessDecisionManager是怎么加载进来的,但是具体用来认证的AuthenticationManager怎么加载还没有说明,这篇文章,我们就重点分析下各种各样的AuthenticationManager是怎么加载的。
  通过上一篇我们知道EnableWebSecurity这个注解除了引入WebSecurityConfiguration这个配置类外,还引入了EnableGlobalAuthentication这个注解,和认证机制相关的秘密就在这个注解中,下面我们重点看看这个注解是怎么做的
环境:
  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 {
}

这个注解主要是引入了AuthenticationConfiguration这个配置类
2. AuthenticationConfiguration类
...
@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;
	}
...

这个配置类最重要的就是上面的这一部分,主要做了下面四件事
  • 注册一个AuthenticationManagerBuilder类型的bean
  • 注册了三个实现了GlobalAuthenticationConfigurerAdapter的bean:GlobalAuthenticationConfigurerAdapter、InitializeUserDetailsBeanManagerConfigurer、InitializeAuthenticationProviderBeanManagerConfigurer
  • 定义了一个获取AuthenticationManager的方法getAuthenticationManager(),这个方法怎么调用下面会讲
  • 通过setGlobalAuthenticationConfigurers这个方法,将实现了GlobalAuthenticationConfigurerAdapter这个抽象类的类注入到当前类中,就是我们上面说的三个bean,实际运行时发现除了上面三个类外,还有
  • 另外两个bean也注入了进来
    BootGlobalAuthenticationConfigurationAdapter和SpringBootAuthenticationConfigurerAdapter
    下面就说下这两个bean是如何创建的
    首先在spring-boot-autoconfigure-{version}.jar的META-INF/spring.factories中配置了springboot启动时自动加载的类
    其中和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,\

    和我们这两个bean相关的类是SecurityAutoConfiguration,这个自动配置类又引入了
    SpringBootWebSecurityConfiguration
    BootGlobalAuthenticationConfiguration
    AuthenticationManagerConfiguration 这三个配置类

    SpringBootWebSecurityConfiguration这个类的功能是只要我们把spring security相关的jar引入,就保证基本的spring security功能能使用,因为我们自己引入EnableWebSecurity,这个注解引入了WebSecurityConfiguration,所以这个配置类就不起作用了
    在BootGlobalAuthenticationConfiguration 这个配置类中,就可以看到定义了我们要找的bean -BootGlobalAuthenticationConfiguration 这个bean的定义也是static的
    AuthenticationManagerConfiguration 这个配置类定义了两个bean,其中一个就是我们要找的 SpringBootAuthenticationConfigurerAdapter 这个bean的定义也是static的
    另一个就是非常重要的 AuthenticationManager这个bean的定义并且是@Primary的
    调用的是 上面提到的AuthenticationConfiguration.getAuthenticationManager()这个方法,但是因为我们在WebSecurityConfigurerAdapter类中配置httpSecurity时已经调用过一次
    所以这个调用这个方法会直接返回。


getAuthenticationManager()这个方法的调用,我们需要重新看下WebSecurityConfigurerAdapter这个类的getHttp()方法
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;
	}

调用过程就是上面这两段代码,默认情况下disableLocalConfigureAuthenticationBldr是true,所以最终调用了authenticationConfiguration.getAuthenticationManager();即上面说的那个方法。
在getAuthenticationManager()方法中,主要是调用AuthenticationManagerBuilder这个类的build方法来获取一个AuthenticationManager,AuthenticationManagerBuilder类和httpSecurity、webSecurity类一样都是继承自AbstractConfiguredSecurityBuilder,所以build的过程也一样 init->configure->performBuild.在这个过程中会依次调用注入的GlobalAuthenticationConfigurerAdapter这些类的configure方法。
下面以InitializeUserDetailsBeanManagerConfigurer这个类来说明
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);
		}
....

这个类在init时向AuthenticationManagerBuilder中新追加了一个InitializeUserDetailsManagerConfigurer类,可以看到在这个类中为我们创建了一个DaoAuthenticationProvider,并把创建好的DaoAuthenticationProvider放入到了AuthenticationManagerBuilder中,在创建过程中使用了applicationcontext中定义过的UserDetailsService和PasswordEncoder类,这就是我们在很多例子中看到的定义一个UserDetailsService就可以实现自己认证逻辑的原因。
其他的Configure实现逻辑差不多,只不过做的事情不一样,就不再一一展开,下面主要看下AuthenticationManagerBuilder的performBuild()方法
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;
	}

在这个方法中用我们之前用的authenticationProviders创建了一个ProviderManager类,还设置了一个parentAuthenticationManager参数,在这个阶段parentAuthenticationManager属性的值是null,下面接着说这个字段在什么时候用。
这个设置还是在WebSecurityConfigurerAdapter这个类的getHttp()方法中,
在这个方法中调用authenticationManager()方法后接着就将创建好的AuthenticationManager设置成authenticationBuilder的parentAuthenticationManager属性,即我们刚才分析的创建过程只是创建了一个parentAuthenticationManager,那在什么时候再继续利用authenticationBuilder创建一个真实的authenticationManager呢,答案在HttpSecurity类中
6. HttpSecurity
@Override
	protected void beforeConfigure() throws Exception {
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
	}

通过上一篇文章,我们知道在HttpSecurity的build方法时,会先调用这个类的beforeConfigure方法,这个时候就会将我们继承了WebSecurityConfigurerAdapter中定义的各种认证方式给注册进来。如我们想用INMEMORY认证方式时可以这样配置
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");
	}
}

或者是利用cas认证时
@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable().authorizeRequests().anyRequest().hasRole("USER").and()
				.exceptionHandling().authenticationEntryPoint(casEntryPoint()).and()
				.addFilter(casFilter())
				.authenticationProvider(casAuthenticationProvider());
	}

因此正常情况下ProviderManager都会有一个parent属性,这个也是为什么在ProviderManager 这个类的authenticate方法中会判断如果当前ProviderManager不能认证成功的话,会再用parent来认证的原因。

至此,spring-security java config中主要的组件加载逻辑已经讲完。

猜你喜欢

转载自fengyilin.iteye.com/blog/2410779
今日推荐