在上一篇文章中通过对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
这个配置类最重要的就是上面的这一部分,主要做了下面四件事
- 注册一个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, 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中主要的组件加载逻辑已经讲完。