乐趣区

关于javascript:深入理解-WebSecurityConfigurerAdapter源码篇

咱们持续来撸 Spring Security 源码,明天来撸一个十分重要的 WebSecurityConfigurerAdapter。

咱们的自定义都是继承自 WebSecurityConfigurerAdapter 来实现的,然而对于 WebSecurityConfigurerAdapter 外部的工作原理,配置原理,很多小伙伴可能都还不太熟悉,因而咱们明天就来捋一捋。

咱们先来看一张 WebSecurityConfigurerAdapter 的继承关系图:

在这层继承关系中,有两个十分重要的类:

  • SecurityBuilder
  • SecurityConfigurer

这两个类松哥在之前的文章中都和大家分享过了,具体参考:

  • 深刻了解 HttpSecurity【源码篇】(本文讲的是 SecurityBuilder 体系)
  • 深刻了解 SecurityConfigurer【源码篇】

所以对于这两个类的介绍以及作用,松哥这里就不赘述了。咱们间接从 WebSecurityConfigurer 开始看起。

1.WebSecurityConfigurer

WebSecurityConfigurer 其实是一个空接口,然而它里边束缚了一些泛型,如下:

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
        SecurityConfigurer<Filter, T> {}

这里边的泛型很要害,这关乎到 WebSecurityConfigurer 的目标是啥!

  1. SecurityBuilder 中的泛型 Filter,示意 SecurityBuilder 最终的目标是为了构建一个 Filter 对象进去。
  2. SecurityConfigurer 中两个泛型,第一个示意的含意也是 SecurityBuilder 最终构建的对象。

同时这里还定义了新的泛型 T,T 须要继承自 SecurityBuilder<Filter>,依据 WebSecurityConfigurerAdapter 中的定义,咱们能够晓得,T 就是 WebSecurity,咱们也大略能猜出 WebSecurity 就是 SecurityBuilder<Filter> 的子类。

所以 WebSecurityConfigurer 的目标咱们能够了解为就是为了配置 WebSecurity。

2.WebSecurity

咱们来看下 WebSecurity 的定义:

public final class WebSecurity extends
        AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
        SecurityBuilder<Filter>, ApplicationContextAware {}

没错,的确是这样!WebSecurity 继承自 AbstractConfiguredSecurityBuilder<Filter, WebSecurity> 同时实现了 SecurityBuilder<Filter> 接口。

WebSecurity 的这些接口和继承类,松哥在后面的源码剖析中都和大家介绍过了,可能有的小伙伴遗记了,我再来和大家温习一下。

AbstractConfiguredSecurityBuilder

首先 AbstractConfiguredSecurityBuilder 中定义了一个枚举类,将整个构建过程分为 5 种状态,也能够了解为构建过程生命周期的五个阶段,如下:

private enum BuildState {UNBUILT(0),
    INITIALIZING(1),
    CONFIGURING(2),
    BUILDING(3),
    BUILT(4);
    private final int order;
    BuildState(int order) {this.order = order;}
    public boolean isInitializing() {return INITIALIZING.order == order;}
    public boolean isConfigured() {return order >= CONFIGURING.order;}
}

五种状态别离是 UNBUILT、INITIALIZING、CONFIGURING、BUILDING 以及 BUILT。另外还提供了两个判断办法,isInitializing 判断是否正在初始化,isConfigured 示意是否曾经配置结束。

AbstractConfiguredSecurityBuilder 中的办法比拟多,松哥在这里列出来两个要害的办法和大家剖析:

private <C extends SecurityConfigurer<O, B>> void add(C configurer) {Assert.notNull(configurer, "configurer cannot be null");
    Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
            .getClass();
    synchronized (configurers) {if (buildState.isConfigured()) {
            throw new IllegalStateException("Cannot apply" + configurer
                    + "to already built object");
        }
        List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
                .get(clazz) : null;
        if (configs == null) {configs = new ArrayList<>(1);
        }
        configs.add(configurer);
        this.configurers.put(clazz, configs);
        if (buildState.isInitializing()) {this.configurersAddedInInitializing.add(configurer);
        }
    }
}
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;
}

第一个就是这个 add 办法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到 configurers 中,未来再对立初始化并配置,configurers 自身是一个 LinkedHashMap,key 是配置类的 class,value 是一个汇合,汇合里边放着 xxxConfigure 配置类。当须要对这些配置类进行集中配置的时候,会通过 getConfigurers 办法获取配置类,这个获取过程就是把 LinkedHashMap 中的 value 拿进去,放到一个汇合中返回。

另一个办法就是 doBuild 办法。

@Override
protected final O doBuild() throws Exception {synchronized (configurers) {
        buildState = BuildState.INITIALIZING;
        beforeInit();
        init();
        buildState = BuildState.CONFIGURING;
        beforeConfigure();
        configure();
        buildState = BuildState.BUILDING;
        O result = performBuild();
        buildState = BuildState.BUILT;
        return result;
    }
}
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 : configurersAddedInInitializing) {configurer.init((B) this);
    }
}
private void configure() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {configurer.configure((B) this);
    }
}

在 AbstractSecurityBuilder 类中,过滤器的构建被转移到 doBuild 办法下面了,不过在 AbstractSecurityBuilder 中只是定义了形象的 doBuild 办法,具体的实现在 AbstractConfiguredSecurityBuilder。

doBuild 办法就是一边更新状态,进行进行初始化。

beforeInit 是一个预留办法,没有任何实现。

init 办法就是找到所有的 xxxConfigure,挨个调用其 init 办法进行初始化。

beforeConfigure 是一个预留办法,没有任何实现。

configure 办法就是找到所有的 xxxConfigure,挨个调用其 configure 办法进行配置。

最初则是 performBuild 办法,是真正的过滤器链构建办法,然而在 AbstractConfiguredSecurityBuilder 中 performBuild 办法只是一个形象办法,具体的实现在它的子类中,也就是 WebSecurityConfigurer。

SecurityBuilder<Filter>

SecurityBuilder 就是用来构建过滤器链的,在 HttpSecurity 实现 SecurityBuilder 时,传入的泛型就是 DefaultSecurityFilterChain,所以 SecurityBuilder#build 办法的性能很明确,就是用来构建一个过滤器链进去,然而那个过滤器链是 Spring Security 中的。在 WebSecurityConfigurerAdapter 中定义的泛型是 SecurityBuilder<Filter>,所以最终构建的是一个一般 Filter,其实就是 FilterChainProxy,对于 FilterChainProxy,大家能够参考深刻了解 FilterChainProxy【源码篇】。

WebSecurity

WebSecurity 的外围逻辑集中在 performBuild 构建办法上,咱们一起来看下:

@Override
protected Filter performBuild() throws Exception {
    Assert.state(!securityFilterChainBuilders.isEmpty(),
            () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified."
                    + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter."
                    + "More advanced users can invoke"
                    + WebSecurity.class.getSimpleName()
                    + ".addSecurityFilterChainBuilder directly");
    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
    for (RequestMatcher ignoredRequest : ignoredRequests) {securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {securityFilterChains.add(securityFilterChainBuilder.build());
    }
    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    if (httpFirewall != null) {filterChainProxy.setFirewall(httpFirewall);
    }
    filterChainProxy.afterPropertiesSet();
    Filter result = filterChainProxy;
    if (debugEnabled) {
        logger.warn("\n\n"
                + "********************************************************************\n"
                + "**********        Security debugging is enabled.       *************\n"
                + "**********    This may include sensitive information.  *************\n"
                + "**********      Do not use in a production system!     *************\n"
                + "********************************************************************\n\n");
        result = new DebugFilter(filterChainProxy);
    }
    postBuildAction.run();
    return result;
}

先来说一句,这里的 performBuild 办法只有一个性能,那就是构建 FilterChainProxy,如果你还不理解什么是 FilterChainProxy,能够参考松哥之前的介绍:深刻了解 FilterChainProxy【源码篇】。

把握住了这条主线,咱们再来看办法的实现就很容易了。

  1. 首先统计过滤器链的总条数,总条数包含两个方面,一个是 ignoredRequests,这是疏忽的申请,通过 WebSecurity 配置的疏忽申请,松哥之前介绍过,参见:Spring Security 两种资源放行策略,千万别用错了!,另一个则是 securityFilterChainBuilders,也就是咱们通过 HttpSecurity 配置的过滤器链,有几个就算几个。
  2. 创立 securityFilterChains 汇合,并且遍历下面提到的两种类型的过滤器链,并将过滤器链放入 securityFilterChains 汇合中。
  3. 我在深刻了解 HttpSecurity【源码篇】一文中介绍过,HttpSecurity 构建进去的过滤器链对象就是 DefaultSecurityFilterChain,所以能够间接将 build 后果放入 securityFilterChains 中,而 ignoredRequests 中保留的则须要重构一下才能够存入 securityFilterChains。
  4. securityFilterChains 中有数据之后,接下来创立一个 FilterChainProxy。
  5. 给新建的 FilterChainProxy 配置上防火墙,防火墙的介绍参考松哥之前的:Spring Security 自带防火墙!你都不晓得本人的零碎有多平安!。
  6. 最初咱们返回的就是 FilterChainProxy 的实例。

从这段剖析中,咱们能够看进去 WebSecurity 和 HttpSecurity 的区别:

  1. HttpSecurity 目标是构建过滤器链,一个 HttpSecurity 对象构建一条过滤器链,一个过滤器链中有 N 个过滤器,HttpSecurity 所做的事件实际上就是在配置这 N 个过滤器。
  2. WebSecurity 目标是构建 FilterChainProxy,一个 FilterChainProxy 中蕴含有多个过滤器链和一个 Firewall。

这就是 WebSecurity 的次要作用,外围办法是 performBuild,其余办法都比较简单,松哥就不一一解释了。

3.WebSecurityConfigurerAdapter

最初咱们再来看 WebSecurityConfigurerAdapter,因为 WebSecurityConfigurer 只是一个空接口,WebSecurityConfigurerAdapter 就是针对这个空接口提供一个具体的实现,最终目标还是为了不便你配置 WebSecurity。

WebSecurityConfigurerAdapter 中的办法比拟多,然而依据咱们后面的剖析,提纲挈领的办法就两个,一个是 init,还有一个 configure(WebSecurity web),其余办法都是为这两个办法服务的。那咱们就来看下这两个办法:

先看 init 办法:

public void init(final WebSecurity web) throws Exception {final HttpSecurity http = getHttp();
    web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
        FilterSecurityInterceptor securityInterceptor = http
                .getSharedObject(FilterSecurityInterceptor.class);
        web.securityInterceptor(securityInterceptor);
    });
}
protected final HttpSecurity getHttp() throws Exception {if (http != null) {return http;}
    AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
    localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    AuthenticationManager authenticationManager = authenticationManager();
    authenticationBuilder.parentAuthenticationManager(authenticationManager);
    Map<Class<?>, Object> sharedObjects = createSharedObjects();
    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
            sharedObjects);
    if (!disableDefaults) {
        // @formatter:off
        http
            .csrf().and()
            .addFilter(new WebAsyncManagerIntegrationFilter())
            .exceptionHandling().and()
            .headers().and()
            .sessionManagement().and()
            .securityContext().and()
            .requestCache().and()
            .anonymous().and()
            .servletApi().and()
            .apply(new DefaultLoginPageConfigurer<>()).and()
            .logout();
        // @formatter:on
        ClassLoader classLoader = this.context.getClassLoader();
        List<AbstractHttpConfigurer> defaultHttpConfigurers =
                SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
        for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {http.apply(configurer);
        }
    }
    configure(http);
    return http;
}
protected void configure(HttpSecurity http) throws Exception {logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin().and()
        .httpBasic();}

init 办法能够算是这里的入口办法了:首先调用 getHttp 办法进行 HttpSecurity 的初始化。HttpSecurity 的初始化,实际上就是配置了一堆默认的过滤器,配置实现后,最终还调用了 configure(http) 办法,该办法又配置了一些拦截器,不过在理论开发中,咱们常常会重写 configure(http) 办法,在松哥本系列后面的文章中,configure(http) 办法简直都有重写。HttpSecurity 配置实现后,再将 HttpSecurity 放入 WebSecurity 中,保留在 WebSecurity 的 securityFilterChainBuilders 汇合里,具体参见:深刻了解 HttpSecurity【源码篇】。

configure(WebSecurity web) 办法实际上是一个空办法,咱们在理论开发中可能会重写该办法(参见 Spring Security 两种资源放行策略,千万别用错了!一文):

public void configure(WebSecurity web) throws Exception {}

4. 小结

这便是 WebSecurityConfigurerAdapter,整体上来说并不难,然而要和松哥后面几篇源码剖析文章一起看,了解会更加粗浅一些。

传送门:

  1. 深刻了解 FilterChainProxy【源码篇】
  2. 深刻了解 SecurityConfigurer【源码篇】
  3. 深刻了解 HttpSecurity【源码篇】
  4. 深刻了解 AuthenticationManagerBuilder【源码篇】

好啦,小伙伴们要是有播种,记得点个在看激励下松哥哦~

退出移动版