SpringSecurity启动流程源码解析 | segmentfault新人第三弹

别辜负生命,别辜负本身。

楔子

前面两期我讲了SpringSecurity认证流程SpringSecurity鉴权流程,今天是第三期,是SpringSecurity的收尾工做,讲SpringSecurity的启动流程。前端

就像不少电影拍火了以后其续做每每是前做的前期故事同样,我这个第三期要讲的SpringSecurity启动流程也是不择不扣的"前期故事",它能帮助你真正认清SpringSecurity的总体全貌。java

在以前的文章里,在说到SpringSecurity中的过滤器链的时候,每每是把它做为一个概念了解的,就是咱们只是知道有这么个东西,也知道它究竟是干什么用的,可是咱们殊不知道这个过滤器链是由什么类何时去怎么样建立出来的。程序员

今天这期就是要了解SpringSecurity的自动配置到底帮咱们作了什么,它是如何把过滤器链给建立出来的,又是在默认配置的时候怎么加入了咱们的自定义配置。web

祝有好收获(边赞边看,法力无限)。面试

1. 📚EnableWebSecurity

咱们先来看看咱们通常是如何使用SpringSecurity的。spring

咱们用SpringSecurity的时候都会先新建一个SpringSecurity相关的配置类,用它继承WebSecurityConfigurerAdapter,而后打上注解@EnableWebSecurity,而后咱们就能够经过重写
WebSecurityConfigurerAdapter里面的方法来完成咱们本身的自定义配置。后端

就像这样:设计模式

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

    }

}

咱们已经知道,继承WebSecurityConfigurerAdapter是为了重写配置,那这个注解是作了什么呢?session

从它的名字@EnableWebSecurity咱们能够大概猜出来,它就是那个帮咱们自动配置了SpringSecurity好心人app

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

    /**
     * Controls debugging support for Spring Security. Default is false.
     * @return if true, enables debug support with Spring Security
     */
    boolean debug() default false;
}

emmm,我猜你们应该有注解相关的知识吧,ok,既然大家都有注解相关的知识,我就直接讲了。

这个@EnableWebSecurity中有两个地方是比较重要的:

  • 一是@Import注解导入了三个类,这三个类中的后两个是SpringSecurity为了兼容性作的一些东西,兼容SpringMVC,兼容SpringSecurityOAuth2,咱们主要看的实际上是第一个类,导入这个类表明了加载了这个类里面的内容。
  • 二是@EnableGlobalAuthentication这个注解,@EnableWebSecurity你们还没搞明白呢,您这又来一个,这个注解呢,其做用也是加载了一个配置类-AuthenticationConfiguration,看它的名字你们也可应该知道它加载的类是什么相关的了吧,没错就是AuthenticationManager相关的配置类,这个咱们能够之后再说。

综上所述,@EnableWebSecurity能够说是帮咱们自动加载了两个配置类:WebSecurityConfigurationAuthenticationConfiguration(@EnableGlobalAuthentication注解加载了这个配置类)。

其中WebSecurityConfiguration是帮助咱们创建了过滤器链的配置类,而AuthenticationConfiguration则是为咱们注入AuthenticationManager相关的配置类,咱们今天主要讲的是WebSecurityConfiguration

2. 📖源码概览

既然讲的是WebSecurityConfiguration,咱们照例先把源码给你们看看,精简了一下可有可无的:

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private ClassLoader beanClassLoader;

    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;

    
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        return webSecurity.build();
    }


    @Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        if (debugEnabled != null) {
            webSecurity.debug(debugEnabled);
        }

        webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

        Integer previousOrder = null;
        Object previousConfig = null;
        for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException(
                        "@Order on WebSecurityConfigurers must be unique. Order of "
                                + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                + config + " too.");
            }
            previousOrder = order;
            previousConfig = config;
        }
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }
    
}

如代码所示,首先WebSecurityConfiguration是个配置类,类上面打了@Configuration注解,这个注解的做用你们还知道吧,在这里就是把这个类中全部带@Bean注解的Bean给实例化一下。

这个类里面比较重要的就两个方法:springSecurityFilterChainsetFilterChainProxySecurityConfigurer

springSecurityFilterChain方法上打了@Bean注解,任谁也能看出来就是这个方法建立了springSecurityFilterChain,可是先别着急,咱们不能先看这个方法,虽然它在上面。

3. 📄SetFilterChainProxySecurityConfigurer

咱们要先看下面的这个方法:setFilterChainProxySecurityConfigurer,为啥呢?

为啥呢?

由于它是@Autowired注解,因此它要比springSecurityFilterChain方法优先执行,从系统加载的顺序来看,咱们须要先看它。

@Autowired在这里的做用是为这个方法自动注入所须要的两个参数,咱们先来看看这两个参数:

  • 参数objectPostProcessor是为了建立WebSecurity实例而注入进来的,先了解一下便可。
  • 参数webSecurityConfigurers是一个List,它其实是全部WebSecurityConfigurerAdapter的子类,那若是咱们定义了自定义的配置类,其实就是把咱们的配置也读取到了。

    这里其实有点难懂为何参数中SecurityConfigurer<Filter, WebSecurity>这个类型能够拿到WebSecurityConfigurerAdapter的子类?

    由于WebSecurityConfigurerAdapter实现了WebSecurityConfigurer<WebSecurity>接口,而WebSecurityConfigurer<WebSecurity>又继承了SecurityConfigurer<Filter, T>,通过一层实现,一层继承关系以后,WebSecurityConfigurerAdapter终于成为了SecurityConfigurer的子类。

    而参数中SecurityConfigurer<Filter, WebSecurity>中的两个泛型参数实际上是起到了一个过滤的做用,仔细查看咱们的WebSecurityConfigurerAdapter的实现与继承关系,你能够发现咱们的WebSecurityConfigurerAdapter正好是这种类型。

ok,说完了参数,我以为咱们能够看看代码了:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
        ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {

    // 建立一个webSecurity实例
    webSecurity = objectPostProcessor
            .postProcess(new WebSecurity(objectPostProcessor));
    if (debugEnabled != null) {
        webSecurity.debug(debugEnabled);
    }

    // 根据order排序
    webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

    Integer previousOrder = null;
    Object previousConfig = null;
    for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
        Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
        if (previousOrder != null && previousOrder.equals(order)) {
            throw new IllegalStateException(
                    "@Order on WebSecurityConfigurers must be unique. Order of "
                            + order + " was already used on " + previousConfig + ", so it cannot be used on "
                            + config + " too.");
        }
        previousOrder = order;
        previousConfig = config;
    }

    // 保存配置
    for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
        webSecurity.apply(webSecurityConfigurer);
    }

    // 成员变量初始化
    this.webSecurityConfigurers = webSecurityConfigurers;
}

根据咱们的注释,这段代码作的事情能够分为觉得几步:

  1. 建立了一个webSecurity实例,而且赋值给成员变量。
  2. 紧接着对webSecurityConfigurers经过order进行排序,order是加载顺序。
  3. 进行判断是否有相同order的配置类,若是出现将会直接报错。
  4. 保存配置,将其放入webSecurity的成员变量中。

你们能够将这些直接理解为成员变量的初始化,和加载咱们的配置类配置便可,由于后面的操做都是围绕它初始化的webSecurity实例和咱们加载的配置类信息来作的。

这些东西还能够拆出来一步步的来说,可是这样的话真是一篇文章写不完,我也没有那么大的精力可以事无巨细的写出来,我只挑选这条痕迹清晰的主脉络来说,若是你们看完能明白它的一个加载顺序其实就挺好了。

就像Spring的面试题会问SpringBean的加载顺序,SpringMVC则会问SpringMVC一个请求的运行过程同样。

所有弄得明明白白,必需要精研源码,在初期,咱们只要知道它的一条主脉络,在以后的使用中,哪出了问题你能够直接去定位到多是哪有问题,这样就已经很好了,学习是一个循环渐进的过程。

4. 📃SpringSecurityFilterChain

初始化完变量,加载完配置,咱们要开始建立过滤器链了,因此先走setFilterChainProxySecurityConfigurer是有缘由的,若是咱们不把咱们的自定义配置加载进来,建立过滤器链的时候怎么知道哪些过滤器须要哪些过滤器不须要。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        return webSecurity.build();
    }

springSecurityFilterChain方法逻辑就很简单了,若是咱们没加载自定义的配置类,它就替咱们加载一个默认的配置类,而后调用这个build方法。

看到这熟悉的方法名称,你就应该知道这是建造者模式,无论它什么模式,既然调用了,咱们点进去就是了。

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");
    }

build()方法是webSecurity的父类AbstractSecurityBuilder中的方法,这个方法又调用了doBuild()方法。

@Override
protected final O doBuild() throws Exception {
    synchronized (configurers) {
        buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
        
        // 空方法
        beforeInit();
        // 调用init方法
        init();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;

        // 空方法
        beforeConfigure();
        // 调用configure方法
        configure();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;

        // 调用performBuild
        O result = performBuild();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;

        return result;
    }
}

经过个人注释能够看到beforeInit()beforeConfigure()都是空方法,
实际有用的只有init()configure()performBuild()方法。

咱们先来看看init()configure()方法。

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);
        }
    }

源码中能够看到都是先获取到咱们的配置类信息,而后循环调用配置类本身的init()configure()方法。

前面说过,咱们的配置类是继承了WebSecurityConfigurerAdapter的子类,而WebSecurityConfigurerAdapter又是SecurityConfigurer的子类,全部SecurityConfigurer的子类都须要实现init()configure()方法。

因此这里的init()configure()方法其实就是调用WebSecurityConfigurerAdapter本身重写的init()configure()方法。

其中WebSecurityConfigurerAdapter中的configure()方法是一个空方法,因此咱们只须要去看WebSecurityConfigurerAdapter中的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);
        });
    }

这里也能够分为两步:

  1. 执行了getHttp()方法,这里面初始化加入了不少过滤器。
  2. HttpSecurity放入WebSecurity,将FilterSecurityInterceptor放入WebSecurity,就是咱们鉴权那章讲过的FilterSecurityInterceptor

那咱们主要看第一步getHttp()方法:

protected final HttpSecurity getHttp() throws Exception {
        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;
    }

getHttp()方法里面http调用的那一堆方法都是一个个过滤器,第一个csrf()很明显就是防止CSRF攻击的过滤器,下面还有不少,这就是SpringSecurity默认会加入过滤器链的那些过滤器了。

其次,还有一个重点就是倒数第二行代码,我也加上了注释,咱们通常在咱们自定义的配置类中重写的就是这个方法,因此咱们的自定义配置就是在这里生效的。

因此在初始化的过程当中,这个方法会先加载本身默认的配置而后再加载咱们重写的配置,这样二者结合起来,就变成了咱们看到的默认配置。(若是咱们不重写configure(http)方法,它也会一点点的默认配置,你们能够去看源码,看了就明白了。)

init()configure()(空方法)结束以后,就是调用performBuild()方法。

protected Filter performBuild() throws Exception {

    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();

    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
            chainSize);

    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }

    // 调用securityFilterChainBuilder的build()方法
    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;
    
    postBuildAction.run();
    return result;
    }

这个方法主要须要看的是调用securityFilterChainBuilderbuild()方法,这个securityFilterChainBuilder是咱们在init()方法中add的那个,因此这里的securityFilterChainBuilder其实就是HttpSecurity,因此这里实际上是调用了HttpSecuritybulid()方法。

又来了,WebSecuritybulid()方法还没说完,先来了一下HttpSecuritybulid()方法。

HttpSecuritybulid()方法进程和以前的同样,也是先init()而后configure()最后performBuild()方法,值得一提的是在HttpSecurityperformBuild()方法里面,会对过滤器链中的过滤器进行排序:

@Override
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }

HttpSecuritybulid()方法执行完了以后将DefaultSecurityFilterChain返回给WebSecurityperformBuil()方法,performBuil()方法再将其转换为FilterChainProxy,最后WebSecurityperformBuil()方法执行结束,返回一个Filter注入成为name="springSecurityFilterChain"Bean

通过以上这些步骤以后,springSecurityFilterChain方法执行完毕,咱们的过滤器链就建立完成了,SpringSecurity也能够跑起来了。

后记

看到这的话,其实你已经颇有耐性了,但可能还以为云里雾里的,由于SpringSecurity(Spring你们族)这种工程化极高的项目项目都是各类设计模式和编码思想满天飞,看不懂的时候只能说这什么玩意,看得懂的时候又该膜拜这是艺术啊。

这些东西它不容易看懂可是比较解耦容易扩展,像一条线下来的代码就容易看懂可是不容易扩展了,福祸相依。

并且这么多名称相近的类名,各类继承抽象,要好好理解下来的确没那么容易,这篇其实想给这个SpringSecurity来个收尾,逼着本身写的,我这我的喜欢善始善终,这段东西也的确复杂,接下来的几篇打算写几个实用的有意思的也轻松的放松一下。

若是你对SpringSecurity源码有兴趣能够跟着来我这个文章,点开你本身的源码点一点,看一看,加油。

自从上篇征文发了以后,感受多了不少前端的关注者,掘金果真仍是前端多啊,没事,虽然我不怎么写前端,说不定哪天改行了呢哈哈。

我也不藏着掖着,其实我如今是写后端的,我对前端呢只能说是略懂略懂,不过无聊了也能够来看看个人文章,点点赞刷刷阅读干干啥的👍,说不定某一天忽然看懂了某篇文还前端劝退后端入行,加油了你们。

别辜负生命,别辜负本身。

大家的每一个点赞收藏与评论都是对我知识输出的莫大确定,若是有文中有什么错误或者疑点或者对个人指教均可以在评论区下方留言,一块儿讨论。

我是耳朵,一个一直想作知识输出的伪文艺程序员,下期见。

相关文章
相关标签/搜索