SpringSecurity启动流程源码解析

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

就像很多电影拍火了之后其续作往往是前作的前期故事一样,我这个第三期要讲的SpringSecurity启动流程也是不择不扣的"前期故事",它能帮助你真正认清SpringSecurity的整体全貌。

在之前的文章里,在说到SpringSecurity中的过滤器链的时候,往往是把它作为一个概念了解的,就是我们只是知道有这么个东西,也知道它到底是干什么用的,但是我们却不知道这个过滤器链是由什么类什么时候去怎么样创建出来的。

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

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

  1. 📚EnableWebSecurity
    我们先来看看我们一般是如何使用SpringSecurity的。

我们用SpringSecurity的时候都会先新建一个SpringSecurity相关的配置类,用它继承WebSecurityConfigurerAdapter,然后打上注解@EnableWebSecurity,然后我们就可以通过重写 WebSecurityConfigurerAdapter里面的方法来完成我们自己的自定义配置。

就像这样:

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

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

}

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

从它的名字@EnableWebSecurity我们可以大概猜出来,它就是那个帮我们自动配置了SpringSecurity的好心人。

@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可以说是帮我们自动加载了两个配置类:WebSecurityConfiguration和AuthenticationConfiguration(@EnableGlobalAuthentication注解加载了这个配置类)。

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

  1. 📖源码概览
    既然讲的是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给实例化一下。

这个类里面比较重要的就两个方法:springSecurityFilterChain和setFilterChainProxySecurityConfigurer。

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

  1. 📄SetFilterChainProxySecurityConfigurer
    我们要先看下面的这个方法:setFilterChainProxySecurityConfigurer,为啥呢?

为啥呢?

因为它是@Autowired注解,所以它要比springSecurityFilterChain方法优先执行,从系统加载的顺序来看,我们需要先看它。

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

参数objectPostProcessor是为了创建WebSecurity实例而注入进来的,先了解一下即可。

参数webSecurityConfigurers是一个List,它实际上是所有WebSecurityConfigurerAdapter的子类,那如果我们定义了自定义的配置类,其实就是把我们的配置也读取到了。

这里其实有点难懂为什么参数中SecurityConfigurer<Filter, WebSecurity>这个类型可以拿到WebSecurityConfigurerAdapter的子类?

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

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

ok,说完了参数,我觉得我们可以看看代码了:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor 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;

  • 根据我们的注释,这段代码做的事情可以分为以为几步:

创建了一个webSecurity实例,并且赋值给成员变量。

紧接着对webSecurityConfigurers通过order进行排序,order是加载顺序。

进行判断是否有相同order的配置类,如果出现将会直接报错。

保存配置,将其放入webSecurity的成员变量中。

大家可以将这些直接理解为成员变量的初始化,和加载我们的配置类配置即可,因为后面的操作都是围绕它初始化的webSecurity实例和我们加载的配置类信息来做的。

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

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

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

  1. 📃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 = AbstractConfiguredSecurityBuilde
    r.BuildState.BUILT;

    return result;
}
  •  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值