logo头像

From zero to HERO

Spring Security 实战干货:你不了解的Spring Security架构

很多教程配置自定义过滤器都没有充分利用Spring Security提供的机制。导致我们需要在Filter内部去定义一个AuthenticationManager或者暴露一个该类型的Bean来处理AuthenticationProvider注册的问题。

Spring Security配置机制

我们已经知道Spring Security的配置都通过HttpSecurity来做,并且我们在以前也对HttpSecurity做了一个入门级别的介绍。为了优化自定义配置,就必须更深入去了解HttpSecurityHttpSecurity说实话不太简单:

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
    // 省略
}

感觉不到的话,再给你看看类图:

为什么要这么复杂?我第一次看到HttpSecurity的结构时我怀疑我自己是不是Java开发。多年以后,当我深入学习了之后才理解了这种设计。作为一个框架,尤其是安全框架,配置必须足够灵活才能适用于更多的业务场景。Spring Security采取了配置与构建分离的架构设计来保证这一点。

配置与构建分离

配置只需要去收集配置项,构建只需要把所有的配置构建成目标对象。各干各的,分离职责,这种做法能够提高代码的可维护性和可读写性。Spring Security利用接口隔离把配置和构建进行高度抽象,提高灵活度,降低复杂度。不过这个体系依然非常庞大。为了降低学习难度需要把大问题拆解成小问题,各个击破,这种学习方法在学习一些复杂的抽象理论时很凑效。

SecurityBuilder

SecurityBuilder就是对构建的抽象。你看上面的类图过于复杂,而看SecurityBuilder就非常的简单了。

public interface SecurityBuilder<O> {
    // 构建
    O build() throws Exception;
}

就一个动作,构建泛化的目标对象O。通过下面这一组抽象和具体的定义我想你应该明白SecurityBuilder了吧。

 // 抽象
 SecurityBuilder -> O
 // 具体
 HttpSecurity->DefaultSecurityFilterChain

一句话,构建的活都是我来干。

AbstractSecurityBuilder

AbstractSecurityBuilder是对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;

}

它通过原子类AtomicBoolean对构建方法build()进行了调用限制:每个目标对象只能被构建一次,避免安全策略发生不一致的情况。构建方法还加了final关键字,不可覆写!构建的核心逻辑通过预留的钩子方法doBuild()来扩展,钩子方法是很常见的一种继承策略。另外AbstractSecurityBuilder还提供了获取已构建目标对象的方法getObject

一句话,构建的活我只干一次。

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);
    // 把某个对象设置为共享,以便于在多个SecurityConfigurer中使用
    <C> void setSharedObject(Class<C> sharedType, C object);
    // 获取某个共享对象
    <C> C getSharedObject(Class<C> sharedType);
    //  添加额外的 AuthenticationProvider
    H authenticationProvider(AuthenticationProvider authenticationProvider);
    //  添加额外的 UserDetailsService
    H userDetailsService(UserDetailsService userDetailsService) throws Exception;
    // 在过滤器链已有的afterFilter类后面注册一个过滤器
    H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
    // 在过滤器链已有的beforeFilter类前面注册一个过滤器
    H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
    // 在过滤器链注册一个过滤器,该过滤器必须在内置注册表 FilterOrderRegistration 中
    H addFilter(Filter filter);

}

HttpSecurityBuilderDefaultSecurityFilterChain的构建进行了增强,为其构建器增加了一些额外的获取配置或管理配置的入口,参见上面的注释。补充一点这个接口最大的功能就是打通了构建和配置的关系,可以操作下面要讲的SecurityConfigurer

一句话,我只构建DefaultSecurityFilterChain

SecurityConfigurer

SecurityConfigurer是对配置的抽象。配置只是手段,构建才是目的,因此配置是对构建的配置。

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
   // 构建器初始化需要注入的配置,用来后续的信息共享
    void init(B builder) throws Exception;
   // 其它的一些必要配置  
    void configure(B builder) throws Exception;
}

SecurityConfigurer有两个方法,都非常重要:

  • 一个是init方法,这个方法你可以认为是SecurityBuilder构造函数的逻辑。如果你想在SecurityBuilder初始化的时候执行一些逻辑或者在后续配置中共享一些变量的话就可以在init方法中去实现。
  • 另一个方法是configure,为SecurityBuilder配置一些必要的属性。

到这里还没完?这两个方法有着明确的先后执行顺序。在一次构建内可能有多个SecurityConfigurer,只有全部的init逐个执行完毕后才会逐个执行configure方法。相关的源码在AbstractConfiguredSecurityBuilder中的标记部分:

     @Override
    protected final O doBuild() throws Exception {
        synchronized (this.configurers) {
            this.buildState = BuildState.INITIALIZING;
            beforeInit();
            // ① 执行所有的初始化方法
            init();
            this.buildState = BuildState.CONFIGURING;
            beforeConfigure();
            // ② 执行所有的configure方法
            configure();
            this.buildState = BuildState.BUILDING;
            O result = performBuild();
            this.buildState = BuildState.BUILT;
            return result;
        }
    }

一句话,配置SecurityBuilder的事都是我来干。

SecurityConfigurerAdapter

SecurityConfigurer在某些场景下是有局限性的,它不能获取正在配置的SecurityBuilder,因此你无法进一步操作SecurityBuilder,配置的扩展性将大打折扣。因此引入了SecurityConfigurerAdapter来扩展SecurityConfigurer


public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {

    private B securityBuilder;

    private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();

    @Override
    public void init(B builder) throws Exception {
    }

    @Override
    public void configure(B builder) throws Exception {
    }
   // 获取正在配置的构建器,以暴露构建器的api
    public B and() {
        return getBuilder();
    }

    protected final B getBuilder() {
        Assert.state(this.securityBuilder != null, "securityBuilder cannot be null");
        return this.securityBuilder;
    }

    //  用复合对象后置处理器去处理对象,以改变一些对象的特性
    @SuppressWarnings("unchecked")
    protected <T> T postProcess(T object) {
        return (T) this.objectPostProcessor.postProcess(object);
    }
    // 添加一个ObjectPostProcessor到符合构建器
    public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
        this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
    }
    // 设置 需要配置的构建器,这样可以让多个SecurityConfigurerAdapter去配置一个SecurityBuilder
    public void setBuilder(B builder) {
        this.securityBuilder = builder;
    }
    // 其它省略
}

这样可以指定SecurityBuilder,而且可以把SecurityBuilder暴露出来,随时随地去调整SecurityBuilder,灵活性大大提高。

具体说的话,你可以通过and()方法获取SecurityBuilder并对SecurityBuilder的其它配置项进行操作,比如上图中SecurityConfigurerAdapter之间的切换。
除此之外还引入了ObjectPostProcessor来后置操作一些并不开放的内置对象。关于ObjectPostProcessor会找个合适的场景去讲解它。

一句话,配置SecurityBuilder不算什么,灵活适配才是花活。

AbstractHttpConfigurer

不是所有的配置都是有用的,有些配置我们希望有个关闭的入口功能。比如csrf功能,控制着csrf的配置的是CsrfConfigurer,如果CsrfConfigurer有一个关闭功能就好了。因此从SecurityConfigurerAdapter衍生出AbstractHttpConfigurer来满足这个需求。

  public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
        extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
    // 关闭当前配置
    @SuppressWarnings("unchecked")
    public B disable() {
        getBuilder().removeConfigurer(getClass());
        return getBuilder();
    }
    //  增强了父类的新增ObjectPostProcessor方法 
    @SuppressWarnings("unchecked")
    public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
        addObjectPostProcessor(objectPostProcessor);
        return (T) this;
    }

}

AbstractHttpConfigurer的实现类非常多,日常的配置项大都由AbstractHttpConfigurer的实现类来控制。

这个类是做定制化配置的一个重要入口之一,如果你想精通Spring Security,这个类一定要掌握。通常我们如果要实现一个Spring Security风格的过滤器配置类,都要继承这个类。

一句话,所有过滤器配置类都可以继承它。

AbstractConfiguredSecurityBuilder

我们希望有多个SecurityConfigurer配置SecurityBuilder,表单登录的、会话管理、csrf等等。用到什么配置什么,让配置基于策略。因此引入了AbstractConfiguredSecurityBuilder

    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
        // 把 objectPostProcessor注入到configurer
        configurer.addObjectPostProcessor(this.objectPostProcessor);
        // 为 SecurityConfigurerAdapter 设置Builder 以便于能够get到   
        // 注意区别于其它SecurityConfigurer
        configurer.setBuilder((B) this);
        add(configurer);
        return configurer;
    }

    public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
        add(configurer);
        return configurer;
    }

通过上面两个apply方法就可以把所有的SecurityConfigurer适配进来,然后通过doBuilder进行分阶段、精细化构建生命周期。你可以在各个生命周期阶段进行一些必要的操作。

一句话,最终的构建都由我来进行精细化处理。

总结

复杂的架构设计往往是为了灵活度,Spring Security正是采用了配置和构建分离的架构设计才让它更加的灵活。如果你掌握了这种设计,你就可以写出非常优雅的配置。

评论系统未开启,无法评论!