MyException - 我的异常网
当前位置:我的异常网» 开源软件 » spring-security(2)java config加载机制-@EnableG

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

www.MyException.Cn  网友分享于:2013-03-09  浏览:0次
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中主要的组件加载逻辑已经讲完。

文章评论

每天工作4小时的程序员
每天工作4小时的程序员
为什么程序员都是夜猫子
为什么程序员都是夜猫子
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
中美印日四国程序员比较
中美印日四国程序员比较
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
程序员都该阅读的书
程序员都该阅读的书
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
漫画:程序员的工作
漫画:程序员的工作
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员应该关注的一些事儿
程序员应该关注的一些事儿
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
Java程序员必看电影
Java程序员必看电影
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
总结2014中国互联网十大段子
总结2014中国互联网十大段子
那些争议最大的编程观点
那些争议最大的编程观点
程序员和编码员之间的区别
程序员和编码员之间的区别
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
代码女神横空出世
代码女神横空出世
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
鲜为人知的编程真相
鲜为人知的编程真相
我的丈夫是个程序员
我的丈夫是个程序员
程序员的鄙视链
程序员的鄙视链
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
编程语言是女人
编程语言是女人
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
10个调试和排错的小建议
10个调试和排错的小建议
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
如何成为一名黑客
如何成为一名黑客
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
我是如何打败拖延症的
我是如何打败拖延症的
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
旅行,写作,编程
旅行,写作,编程
 程序员的样子
程序员的样子
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
老程序员的下场
老程序员的下场
一个程序员的时间管理
一个程序员的时间管理
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有