Spring Security原理篇(二) 过滤器原理

上篇文章通过对WebSecurityConfiguration这个配置类的源码阅读,已经了解到,在启动的时候主要创建了两个对象,WebSecurity和名字为springSecurityFilterChainFilter。这篇文章主要是通过源码阅读,查看一下Filter的创建过程,以及后面的工作原理。

1. Filter的创建

1.1再看Filter的创建

/**
     * Creates the Spring Security Filter Chain
     * @return the {@link Filter} that represents the security filter chain
     * @throws Exception
     */
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        //T1 查看是否有WebSecurityConfigurer的相关配置
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        //T2 如果没有,说明我们没有注入继承WebSecurityConfigurerAdapter 的对象
        if (!hasConfigurers) {
                        
            //T3 创建默认的配置信息WebSecurityConfigurerAdapter ,保证Spring Security的最基础的功能,如果我们要有自定义的相关,一定要重写配置
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            //T4 默认配置信息载入webSecurity
            webSecurity.apply(adapter);
        }
        // T5这里build一个Filter
        return webSecurity.build();
    }
  • T1,T2,T3,T4处的代码请查看代码的注释,这里重点讲解T5处的代码
  • webSecurity对象在此时已经加载完所有的配置
  • webSecurity对象为我们创建一个Filter通过的是build()方法

1.2 WebSecuritybuild()方法

  • WebSecurity继承了AbstractConfiguredSecurityBuilder类,实现了SecurityBuilder接口.事实上AbstractConfiguredSecurityBuilder类也是实现了SecurityBuilder接口。子类和父类实现同一个接口。事实上是为了子类在反射调用方法getInterfaces()中可以获取到接口,根据这里的情况就是WebSecurity反射调用getInterfaces()可以获取到SecurityBuilder接口(个人理解,也许编写者是另有深意)。

  • WebSecurity的继承关系,了解继承关系,对于后面的代码的理解大有好处

    WebSecurity类图

  • SecurityBuilder定义了构建的接口标准

  • AbstractSecurityBuilder实现build方法,用AtomicBoolean的变量building保证多线程情况下,操作的原子性。此处采用的是模板模式。定义了doBuild()抽象方法

  • AbstractConfiguredSecurityBuilder 继承AbstractSecurityBuilder实现doBuild()方法,也采用模板模式,定义了实现的具体的步骤,如UNBUILTINITIALIZING,CONFIGURING,BUILDING,以及BUILT

1.2.1 SecurityBuilder接口定义

public interface SecurityBuilder<O> {

    /**
     *
     * Builds the object and returns it or null.
     * @return the Object to be built or null if the implementation allows it.
     * @throws Exception
     *
     */
    O build() throws Exception;
}
  • 该接口的作用是定义一个构建的对象标准
  • 只定义了build()方法,返回值是一个泛型
  • 我们看一下WebSecurity的定义
public final class WebSecurity extends
        AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
        SecurityBuilder<Filter>, ApplicationContextAware {
        ...
        ...
}
  • 从上面的代码可以看出WebSecurity指定泛型的类型为Filter,结合上面接口build()方法我们可以知道,WebSecuritybuild()方法返回的是一个Filter,Spring Securiy 通过这个来创建一个过滤器

1.2.2 WebSecuritybuild()过程

  • 从上面WebSecurity的类图来看,AbstractSecurityBuilder保证了线程的安全,AbstractConfiguredSecurityBuilder保证了构建过程以及构建状态。WebSecurity通过performBuild()来实现自身的构建
  • AbstractConfiguredSecurityBuilder的构建过程,我们看一下doBuild()方法的定义
@Override
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;
            beforeInit(); //默认什么都没做,WebSecurity也没有重写
            init(); //T1 默认此处调用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法
            buildState = BuildState.CONFIGURING;

            beforeConfigure(); //默认什么都不做,WebSecurity没有重写
            configure();//调用WebSecurityConfigurerAdapter的configure(WebSecurity web),但是什么都没做

            buildState = BuildState.BUILDING;

            O result = performBuild(); //T2这里调用WebSecurity的performBuild()方法

            buildState = BuildState.BUILT;

            return result; //从WebSecurity的实现,这里返回了一个Filter,完成构建过程
        }
    }
  • T1处调用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法,看一下源代码的定义
    /**
     * @param web
     * @throws Exception
     */
    public void init(final WebSecurity web) throws Exception {
        //构建HttpSecurity对象
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }

这里构建了HttpSecurity对象,以及有一个共享对象FilterSecurityInterceptor ,我们还是要将完整的流程讲完,在下一篇文章对HttpSecurity对象做专门的解读,因为这个HttpSecurity对象扮演者非常重要的角色

  • T2 , 根据WebSecurity的属性构建Filter的performBuild()方法
@Override
    protected Filter performBuild() throws Exception {
        Assert.state(
                //T1  securityFilterChainBuilders哪里来的?
                !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");
               //T2 ignoredRequests.size()到底是什么?
        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
                //这个securityFilterChains 的集合里面存放的就是我们所有的过滤器链,根据长度的定义,我们也可以知道分为两种一个是通过ignoredRequests来的过滤器链,一个是通过securityFilterChainBuilders这个过滤器链构建链来的。
        List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
                chainSize);

                //如果是ignoredRequest类型的,那么久添加默认过滤器链(DefaultSecurityFilterChain)
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
               
                //如果是securityFilterChainBuilder类型的,那么通过securityFilterChainBuilder的build()方法来构建过滤器链
        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
               
            //将过滤器链交给一个过滤器链代理对象,而这个代理对象就是返回回去的过滤器,后面的代码先不做交代。到这里为止,过滤器的过程已经结束
             //T3 什么是FilterChainProxy? 
        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;
    }

从上面源码和标注的注释(也许理解不透彻)来看,其实我们只要回答清楚上面的T1,T2,T3这三个问题,就基本弄清楚了。

1.2.3 WebSecuritysecurityFilterChainBuilders属性哪里来的

  • 我们在上面说到AbstractConfiguredSecurityBuilderdoBuild()里面的init()方法实际上调用的是WebSecurityConfigurerAdapter的init(final WebSecurity web)方法,前面没有详细解释,这里再次引入源码
       /**
     * @param web
     * @throws Exception
     */
    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
               
                //T1
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }
  • T1处调用了WebSecurityaddSecurityFilterChainBuilder()方法,我们看一下这个方法的源代码
public WebSecurity addSecurityFilterChainBuilder(
            SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
                 //这个就是securityFilterChainBuilders变量
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }
  • 从上面两处代码中我们似乎已经知道,在构建Filter过程的初始化的时候,我们对securityFilterChainBuilders这个变量进行了赋值,默认情况下securityFilterChainBuilders里面只有一个对象,那就是HttpSecurity

1.2.4 ignoredRequests到底是什么

  • 其实这个非常简单ignoredRequests只是WebSecurity的一个属性
  • ignoredRequests的list中的值从哪里来的呢,其实我们可以看到里面有一个ignore()方法,通过这个来进行设置的
  • 怎么设置呢,那我们得看看WebSecurityConfigurerAdapter这个类了,里面有一个configure方法供我们重写
public void configure(WebSecurity web) throws Exception {
}
  • 具体的例子如下
@Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring()
           .mvcMatchers("/favicon.ico", "/webjars/**", "/css/**");
    }

  • 然后值得一提的是这里有多少个mvcMatchers就会创建多少个ignoredRequests的对象,也就会有多少个过滤器链,至于源码,可以自行阅读,也是在WebSecurity里面定义的内部类IgnoredRequestConfigurer这个类里面

1.2.5 FilterChainProxy到底是什么

  • 上面的描述中我们知道, FilterChainProxy是真正返回的Filter,上面代码中 FilterChainProxy的对象创建的源码为:
 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
  • 然后这个FilterChainProxy又是比较复杂的玩意儿,我们还是在接下来文章继续吧。

1.3 过滤器实现的流程

  • 虽然到目前为止,整个还是没有交代清楚,但是我们还是需要画一个小图来整理一下前面的流程吧。为接下来HttpSecurityFilterChainProxy类解读做点准备
Filter创建过程.png

我们暂时的放过一下FilterChainProxy,下一篇我们继续HttpSecurity

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 前言 最近在弄微信的OAuth认证,我们认证服务及作为客户端又作为认证服务端。因此需要对spring cloud ...
    不小下阅读 11,921评论 3 54
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,733评论 0 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,559评论 18 139
  • 刚走到这条小巷就看到了这位在学习的小男孩,静悄悄地走过去,偷偷摸摸地拍一张。 一个好可爱的小姑娘,当我拿起手机要拍...
    悬树阅读 483评论 2 2
  • 自从怀孕,每天都在提心掉胆中度过,害怕自己的一个小习惯或者一个不经意伤害到腹中的宝宝,却也是在大大咧咧中度过,从不...
    蝶澈心涯阅读 582评论 0 0