• 综上所述,@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在这里的作用是为这个方法自动注入所需要的两个参数,我们先来看看这两个参数:

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

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

    大家可以将这些直接理解为成员变量的初始化,和加载我们的配置类配置即可,因为后面的操作都是围绕它初始化的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);
            });
        }
    

    这里也可以分为两步:

    那我们主要看第一步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源码有兴趣可以跟着来我这个文章,点开你自己的源码点一点,看一看,加油。

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

    我也不藏着掖着,其实我现在是写后端的,我对前端呢只能说是略懂略懂,不过无聊了也可以来看看我的文章,点点赞刷刷阅读干干啥的👍,说不定某一天突然看懂了某篇文还前端劝退后端入行,加油了大家。

    别辜负生命,别辜负自己。

    你们的每个点赞收藏与评论都是对我知识输出的莫大肯定,如果有文中有什么错误或者疑点或者对我的指教都可以在评论区下方留言,一起讨论。

    07-15 20:57