全局和局部AuthenticationManager

143 阅读4分钟

最近在尝试使用springsecurity新版本做一个授权服务器的功能,在使用过程中结合查资料发现springsecurity的认证管理器有局部和全局的概念,这里通过进行源码的分析,找到相关证明。

Spring Security 5.7 开始WebSecurityConfigurerAdapter 被标记为 @Deprecated(不推荐使用),并在 Spring Security 6 中被移除 推荐我们使用如下方式直接构建过滤器链

@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
        throws Exception {
        
        // 框架会先获取一个新的 HttpSecurity 实例
        // 相当于:http = applicationContext.getBean(HttpSecurity.class);
    http
            .authorizeHttpRequests((authorize) -> authorize
                    .anyRequest().authenticated()
            )
            // Form login handles the redirect to the login page from the
            // authorization server filter chain
            .formLogin(new FormLoginConfigurerCustomer());

    return http.build();
}

1. HttpSecurity http 参数从哪来?

Spring 容器会自动为这个 @Bean 方法注入一个全新的 HttpSecurity 实例

👉 每次创建 SecurityFilterChain Bean 时,都会传入一个“干净的、新创建的” HttpSecurity 实例。

之所以每次都是一个全新的实例 是因为HttpSecurityConfiguration类中有如下配置

@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
   LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
   AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
         this.objectPostProcessor, passwordEncoder);
   authenticationBuilder.parentAuthenticationManager(authenticationManager());
   authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
   HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
   WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
   webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
   // @formatter:off
   http
      .csrf(withDefaults())
      .addFilter(webAsyncManagerIntegrationFilter)
      .exceptionHandling(withDefaults())
      .headers(withDefaults())
      .sessionManagement(withDefaults())
      .securityContext(withDefaults())
      .requestCache(withDefaults())
      .anonymous(withDefaults())
      .servletApi(withDefaults())
      .apply(new DefaultLoginPageConfigurer<>());
   http.logout(withDefaults());
   // @formatter:on
   applyCorsIfAvailable(http);
   applyDefaultConfigurers(http);
   return http;
}

@Scope("prototype") 确保了每次都能是独立的实例

@Scope("prototype") → 每次 getBean("httpSecurity") 都会重新执行 httpSecurity() 方法,创建新对象

HttpSecurity的创建

 // 1 创建一个AuthenticationManagerBuilder
 AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
         this.objectPostProcessor, passwordEncoder);
   // 2设置父级认证管理器      
   authenticationBuilder.parentAuthenticationManager(authenticationManager());
   authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
   //3 创建HttpSecurity
   HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());

构造器如下图:

image.png

所以每个httpScurity内部都有一个独立的AuthenticationManagerBuilder 保存在sharedObjects属性中 这个是共享属性,确保后续使用的时候可以从共享属性中再次获取到

AuthenticationManagerBuilder

AuthenticationManagerBuilder是用来构建AuthenticationManager认证管理器的,每个HttpSecurity都持有一个自己的AuthenticationManagerBuilder。

AuthenticationManager是认证管理器,并在身份认证成功后返回一个经过认证的 Authentication 对象。AuthenticationManager 是一个接口,ProviderManager实现了这个接口

ProviderManager 维护了一个 AuthenticationProvider 列表,每个 AuthenticationProvider 都是一个认证器,不同的 AuthenticationProvider 用来处理不同的 Authentication 对象的认证。一次完整的身份认证流程可能会经过多个 AuthenticationProvider。

image.png

每一个 ProviderManager 管理多个 AuthenticationProvider,同时每一个 ProviderManager 都可以配置一个 parent,如果当前的 ProviderManager 中认证失败了,还可以去它的 parent 中继续执行认证

image.png

根据配置方式和作用范围,我们可以将 AuthenticationManager 分为:

  • “全局” :指被注册为 Spring Bean 的、可被整个应用共享的 AuthenticationManager
  • “局部” :指仅在某个 SecurityFilterChain 内部创建并使用的 AuthenticationManager,作用范围有限

全局认证管理器

DefaultPasswordEncoderAuthenticationManagerBuilder(
         this.objectPostProcessor, passwordEncoder);
   authenticationBuilder.parentAuthenticationManager(authenticationManager());

前面我们了解到构建httpsecurity的时候需要创建authenticationBuilder,authenticationBuilder会通过调用authenticationManager()方法来设置全局认证管理器

private AuthenticationManager authenticationManager() throws Exception {
   return this.authenticationConfiguration.getAuthenticationManager();
}

底层调用的是AuthenticationConfiguration中的方法

public AuthenticationManager getAuthenticationManager() throws Exception {
   if (this.authenticationManagerInitialized) {
      return this.authenticationManager;
   }
   AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
   if (this.buildingAuthenticationManager.getAndSet(true)) {
      return new AuthenticationManagerDelegator(authBuilder);
   }
   for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
      authBuilder.apply(config);
   }
   this.authenticationManager = authBuilder.build();
   if (this.authenticationManager == null) {
      this.authenticationManager = getAuthenticationManagerBean();
   }
   this.authenticationManagerInitialized = true;
   return this.authenticationManager;
}

上面这段方法确保了全局认证管理器只能被初始化一次,调用多次返回的是已经构建好的,基本原理还是从容器中获取到AuthenticationManagerBuilder,调用这个builder的build方法来构建。下面的代码展示了builder是如何放进容器的

@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

   
   // 在容器中放了一个AuthenticationManagerBuilder 将来用来构建全局认证管理器
   @Bean
   public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
         ApplicationContext context) {
      LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
      AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
      DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
            objectPostProcessor, defaultPasswordEncoder);
      if (authenticationEventPublisher != null) {
         result.authenticationEventPublisher(authenticationEventPublisher);
      }
      return result;
   }

局部认证管理器

根据前面的介绍,HttpSecurity 在构建的时候就会传入 AuthenticationManagerBuilder,传入进来的 AuthenticationManagerBuilder ,被保存到SharedObject 里边去了缓存了起来

最终httpsecurity在build的过程中 会先执行beforeConfigure方法,用来构建局部认证管理器

@Override
protected void beforeConfigure() throws Exception {
   if (this.authenticationManager != null) {
      setSharedObject(AuthenticationManager.class, this.authenticationManager);
   }
   else {
      ObservationRegistry registry = getObservationRegistry();
      AuthenticationManager manager = getAuthenticationRegistry().build();
      if (!registry.isNoop() && manager != null) {
         setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager));
      }
      else {
         setSharedObject(AuthenticationManager.class, manager);
      }
   }
}

你会发现getAuthenticationRegistry中返回的就是提前缓存好的authenticationManagerBuilder,最终构建出来的认证管理器是局部管理器

@Override
public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
   getAuthenticationRegistry().authenticationProvider(authenticationProvider);
   return this;
}

@Override
public HttpSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception {
   getAuthenticationRegistry().userDetailsService(userDetailsService);
   return this;
}

private AuthenticationManagerBuilder getAuthenticationRegistry() {
   return getSharedObject(AuthenticationManagerBuilder.class);
}

总结

总结下 本文主要讲解了 1.httpsecurity采用了多实例的方式,作者这么设计的原因是因为一个应用可以配置多条过滤器链,每个过滤器链需要单独的进行配置,httpSecurity就是过滤器链的配置类,所以需要多实例。

2.有全局认证管理器和局部认证管理器,全局认证管理器使用容器中的builder进行构建的,整个应用共享 局部认证管理器是和每个过滤器链,也就是每个HttpSecurity实例绑定的。