Spring Security过滤器链的初始化(中)

230 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

SecurityBuilder的功能

\quadSpring Security 中所有需要构建的对象都可以通过SecurityBuilder来实现,比如说这些默认的过滤器链、代理过滤器、AuthenticationManager等组件,都由SecurityBuilder 进行构建。SecurityBuilder源码本身比较简单,只有一个build方法,返回具体构建的对象泛型O,不同的实现可以构造出不同的SecurityBuilder对象,但是,SecurityBuilder的继承和实现比较复杂,关系图如下。

image.png

1. HttpSecurityBuilder

\quadHttpSecurityBuilder是用来构建HttpSecurity对象的,HttpSecurityBuilder 也是一个接口,其代码如下:

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
      extends SecurityBuilder<DefaultSecurityFilterChain> {

   <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);

   <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);

   <C> void setSharedObject(Class<C> sharedType, C object);

   <C> C getSharedObject(Class<C> sharedType);

   H authenticationProvider(AuthenticationProvider authenticationProvider);

   H userDetailsService(UserDetailsService userDetailsService) throws Exception;

   H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);

   H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);

   H addFilter(Filter filter);

}

我们简单阅读一下这段源码,看一下代码上的注释,了解了解功能:

  1. HtpSecunityBuilder对象本身在定义时就有一个泛型,这个泛型采用了上界通配符,但是HtpSecunityBuilder的子类默认情况下就只有一个,那就是HtpSecurity,所以在阅读的时暂且把接口HtpSecunityBuilder中的H都当成HttpSecurity来理解。
  2. HttpSecurityBuilder 继承自 SecurityBuilder 接口,同时也指定了SecurityBuilder 中的泛型为DefaultSecurityFilterChain,也就是说,HttpSecurityBuilder 想要构建的对象是 DefaultSecurityFilterChain。
  3. getConfigurer 方法是通过类名来获取一个配置器,所谓的配置器就是一个具体的Configurer配置类。
  4. removeConfigurer方法也是按类名移除一个配置器(相当于从Spring Security过滤器链中移除一个过滤器)。
  5. setSharedObject/getSharedObject 这两个方法用来设置或者获取一个由多个配置器共享的对象。
  6. authenticationProvider 方法可以用来配置一个认证器AuthenticationProvider.
  7. userDetailsService 方法可以用来配置一个数据源UserDetailsService。
  8. addFilterAfter/addFilterBefore 方法表示在某一个过滤器之后或者之前添加一个自定义的过滤器。
  9. addFilter 方法可以添加一个过滤器,这个过滤器必须是Spring Security 框架提供的过滤器的一个实例或者其扩展,添加完成后,会自动进行过滤器的排序。

2. AbstractSecurityBuilder

AbstractSecurityBuilder是一个抽象类,它实现了SecurityBuilder 接口,并实现了build方法,它是一个基本的SecurityBuilder,主要功能就是确保正在构建的对象只构建一次,源码如下:

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {

   private AtomicBoolean building = new AtomicBoolean();

   private O object;

   @Override
   public final O build() throws Exception {
      if (this.building.compareAndSet(false, true)) {
         this.object = doBuild();
         return this.object;
      }
      throw new AlreadyBuiltException("This object has already been built");
   }

   public final O getObject() {
      if (!this.building.get()) {
         throw new IllegalStateException("This object has not been built");
      }
      return this.object;
   }

   protected abstract O doBuild() throws Exception;

}

\quad首先,声明了building 变量,这是一个可以自动更新的boolean值,确保即使在多线程环境下,配置类也只能构建一次。
\quad然后,对build方法进行重写,并且设置为final, 这样在AbstractSecurityBuilder的子类中 将不能覆盖build方法了。在build方法内部,通过building变量来控制配置类只构建一次,否则就会抛异常,具体的构建操作交由doBuild方法去完成。
\quad其次,getObject 方法用来返回构建的对象。
\quad最后,doBuild方法是具体的构建方法,该访法在AbstractSecurityBuilder中是一个抽象方法,那么其具体的实现则在其子类中。

3. AbstractConfiguredSecurityBuilder

\quadAbstractConfiguredSecurityBuilder类里面有一个枚举类,这个枚举类主要列举了构建过程中的状态。
\quadAbstractConfiguredSecurityBuilder类里维护了configurers变量,这是一个Map类型的,用来保存所有的配置类。针对configurers 变量,我们可以进行添加配置、移除配置等操作,其主要作用如下:

  • 首先声明了一个configurers变量,用来保存所有的配置类, key是配置类Class对象,value是一个List集合中放着的配置类列表。
  • apply方法有两个,参数类型略有差异,主要功能基本相同,都是向configurers 变量中添加配置类,具体的添加过程则是调用该类里的add方法。
  • add方法用来将所有的配置类保存到configurers 中,在添加的过程中,如果 allowConfigurersOfSameType变量为true,也就是表示允许相同类型的配置类存在,表示List集合中可以存在多个相同类型的配置类。默认情况下,List集合中的配置类只有一个配置类; 如果在AuthenticationManagerBuilder中设置allowConfigurersOfSameType为true,此时相同类型的配置类可以有多个。
  • getConfigurers(Class)方法可以从configurers中返回某个配置类对应的所有实例。而getConfigurer方法也是获取配置类实例,但是只获取集合中第一项。
  • removeConfigurers方法可以从configurers中移除某个配置类对应的所有实例,并返回被移除掉的配置类的实例集合。removeConfigurer方法可以从configurers中移除某一个配置类对应的所有配置类实例,并返回被移除掉的配置类实例中的第一项。
  • getConfigurers方法是一个私有方法,主要是把所有的配置类实例放到一个集合中返回。在配置类初始化和配置的时候,会调用到该方法。

\quad接下来才是AbstractConfiguredSecurityBuilder的重头戏——doBuild方法,doBuild方法也该类的核心构建方法。具体代码如下:

@Override
protected final O doBuild() throws Exception {
   synchronized (this.configurers) {
      this.buildState = BuildState.INITIALIZING;
      beforeInit();
      init();
      this.buildState = BuildState.CONFIGURING;
      beforeConfigure();
      configure();
      this.buildState = BuildState.BUILDING;
      O result = performBuild();
      this.buildState = BuildState.BUILT;
      return result;
   }
}

protected void beforeInit() throws Exception {
}

protected void beforeConfigure() throws Exception {
}

protected abstract O performBuild() throws Exception;

@SuppressWarnings("unchecked")
private void init() throws Exception {
   Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
   for (SecurityConfigurer<O, B> configurer : configurers) {
      configurer.init((B) this);
   }
   for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
      configurer.init((B) this);
   }
}

@SuppressWarnings("unchecked")
private void configure() throws Exception {
   Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
   for (SecurityConfigurer<O, B> configurer : configurers) {
      configurer.configure((B) this);
   }
}

private Collection<SecurityConfigurer<O, B>> getConfigurers() {
   List<SecurityConfigurer<O, B>> result = new ArrayList<>();
   for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
      result.addAll(configs);
   }
   return result;
}
  1. 在doBuild方法中,步骤比较清晰,更新构建状态的同时执行构建方法。构建方法中, beforeInit是一个空的初始化方法,如果需要在初始化之前做准备工作,则可以通过重写该方法进行准备工作。
  2. init方法是初始化方法,也可以理解为所有配置类的初始化方法,在该方法中,主要操作就是对配置类进行遍历,然后调用其init方法完成初始化操作。
  3. beforeConfigure方法可以在configure方法执行之前做一些准备操作。该方法默认也是一个空方法。
  4. configure方法用来完成所有配置类的配置,跟init方法类似,在configure方法中,也是遍历所有的配置类,再分别调用各自的configure方法进行配置。
  5. performBuild方法用来做最后的构建操作,前面的准备工作完成后,最后在performBuild方法中完成构建,这是一个抽象方法,具体的实现过程则在不同的配置类中。

\quad这些就是AbstractConfiguredSecurityBuilder 中最主要的几个方法了