關(guān)于spring security中Filter的創(chuàng)建和工作原理

話不多說(shuō),直接進(jìn)入主題,關(guān)于spring security oauth認(rèn)證原理我已經(jīng)在上一篇博客Spring security oauth2認(rèn)證流程分析分析過(guò)源碼闸氮,里面有一個(gè)名稱為springSecurityFilterChain的過(guò)濾器鏈,也正是這個(gè)過(guò)濾器鏈基本承載了spring security的核心功能,下面我們就來(lái)分析一下這個(gè)過(guò)濾器鏈的創(chuàng)建過(guò)程和工作原理嵌纲。

1.配置的讀取

WebSecurityConfiguration
首先,咱們先看這個(gè)方法腥沽,明眼人一看到@Bean就明白了

    /**
     * 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 {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        // 前面不用關(guān)心逮走,只關(guān)心這段代碼
        return webSecurity.build();
    }

重點(diǎn)是webSecurity.build(),但是大家可能會(huì)有疑問(wèn)今阳,不知道webSecurity啥時(shí)候創(chuàng)建的师溅,也不知道webSecurity里面有些啥?
再往下看:

@Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        //創(chuàng)建一個(gè)WebSecurity對(duì)象,這里就是創(chuàng)建來(lái)webSecurity
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        // 排序可以忽略盾舌,不是重點(diǎn)
        Collections.sort(webSecurityConfigurers, 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;
        }
        // 上面都是排序墓臭,不用管,下面才是重點(diǎn)
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            // 把Filter相關(guān)配置添加進(jìn)去
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }

下面繼續(xù)分析webSecurityConfigurers配置到底是從哪兒來(lái)的矿筝,我們發(fā)現(xiàn)了

@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers

這樣一段代碼起便,咱平時(shí)也沒(méi)見(jiàn)過(guò)這樣的代碼,應(yīng)該也是spring的相關(guān)功能,連猜帶蒙榆综,就在idea中搜索一下AutowiredWebSecurityConfigurersIgnoreParents這個(gè)類妙痹,還真的有這么一個(gè)類:

/**
 * A class used to get all the {@link WebSecurityConfigurer} instances from the current
 * {@link ApplicationContext} but ignoring the parent.
 *
 * @author Rob Winch
 *
 */
final class AutowiredWebSecurityConfigurersIgnoreParents {

    private final ConfigurableListableBeanFactory beanFactory;

    public AutowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        Assert.notNull(beanFactory, "beanFactory cannot be null");
        this.beanFactory = beanFactory;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
        // 查詢?nèi)萜髦兴蠾ebSecurityConfigurer的實(shí)例
        Map<String, WebSecurityConfigurer> beansOfType = beanFactory
                .getBeansOfType(WebSecurityConfigurer.class);
        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
            // 將所有的WebSecurityConfigurer實(shí)例放進(jìn)列表
            webSecurityConfigurers.add(entry.getValue());
        }
        return webSecurityConfigurers;
    }
}

并且也找到了getWebSecurityConfigurers方法,類上面的注釋說(shuō)這個(gè)類是用來(lái)獲取所有spring中WebSecurityConfigurer子類實(shí)例的鼻疮,根據(jù)代碼也能映證注釋的意思怯伊。
咱們?cè)倩氐絎ebSecurityConfiguration類,正好可以找到

    @Bean
    public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }

也就是說(shuō)AutowiredWebSecurityConfigurersIgnoreParents已經(jīng)被容器管理起來(lái)了判沟。
這樣的話耿芹,咱們之前的邏輯也就銜接上了,無(wú)論是資源服務(wù)挪哄,還是認(rèn)證服務(wù)吧秕,亦或是繼承了WebSecurityConfigurerAdapter類,還是實(shí)現(xiàn)了WebSecurityConfigurer迹炼,都會(huì)被AutowiredWebSecurityConfigurersIgnoreParents讀進(jìn)列表砸彬,并添加到一個(gè)WebSecurity對(duì)象里面去。

2.配置生成Filter

咱們回到webSecurity.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");
    }

通過(guò)idea工具進(jìn)入doBuild()方法斯入,進(jìn)入到AbstractConfiguredSecurityBuilder

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

上面就是典型的模版模式了砂碉,記住當(dāng)前對(duì)象是WebSecurity,在調(diào)用init()方法時(shí)刻两,會(huì)循環(huán)去調(diào)用所有WebSecurityConfigurer對(duì)象的init()方法

private void init() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            //循環(huán)調(diào)用WebSecurityConfigurerAdapter的init()方法
            configurer.init((B) this);
        }

        for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
            configurer.init((B) this);
        }
    }

以上init()方法調(diào)用完畢后增蹭,也就是配置信息初始化后,WebSecurity里面的securityFilterChainBuilders屬性就會(huì)生成多個(gè)HttpSecurity對(duì)象磅摹,每一個(gè)HttpSecurity都是和之前的WebSecurityConfigurer對(duì)象一一對(duì)應(yīng)滋迈。
HttpSecurity中會(huì)有一個(gè)configurers屬性,里面全部都是各種Filter的配置對(duì)象偏瓤,都是AbstractHttpConfigurer的子類杀怠。例如:CsrfConfigurer就是CsrfFilter的配置類。
在后續(xù)的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");
        // 其實(shí)就是獲取HttpSecurity的數(shù)量厅克,因?yàn)閟ecurityFilterChainBuilders數(shù)量和HttpSecurity一致赂弓,ignoredRequests一般情況為0.
        int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
        List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
                chainSize);
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        // 重點(diǎn)在securityFilterChainBuilder.build()刻伊,其實(shí)就是HttpSecurity.build()
        // 最終發(fā)現(xiàn)又回到了之前的模版方法盒至,不同的是之前是WebSecurity缺谴,現(xiàn)在是HttpSecurity
        // 在調(diào)用HttpSecurity的configure()方法的時(shí)候,其實(shí)是在循環(huán)調(diào)用configurers屬性里面每一個(gè)AbstractHttpConfigurer的configure()方法
        // 通過(guò)循環(huán)調(diào)用AbstractHttpConfigurer的configure()方法女责,給HttpSecurity創(chuàng)建了一系列的Filter漆枚,存放在屬性filters中
        // 最后通過(guò)HttpSecurity的performBuild()創(chuàng)建了一個(gè)DefaultSecurityFilterChain對(duì)象返回并存放在securityFilterChains中
        for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }
        // 通過(guò)securityFilterChains創(chuàng)建最終的FilterChainProxy,并返回抵知,存放在spring容器中墙基,最后由spring容器設(shè)置到servlet過(guò)濾器鏈中
        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;
    }

通過(guò)以上過(guò)程软族,我們簡(jiǎn)單地跟蹤了FilterChainProxy(也就是springSecurityFilterChain)的創(chuàng)建過(guò)程。

3.FilterChainProxy如何工作

作為一個(gè)Filter残制,第一反應(yīng)就是看doFilter()方法:

@Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                doFilterInternal(request, response, chain);
            }
            finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        }
        else {
            doFilterInternal(request, response, chain);
        }
    }

好像也沒(méi)做啥立砸,重點(diǎn)在doFilterInternal()方法:

private void doFilterInternal(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        FirewalledRequest fwRequest = firewall
                .getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = firewall
                .getFirewalledResponse((HttpServletResponse) response);
        //根據(jù)請(qǐng)求匹配對(duì)應(yīng)的Filter列表
        List<Filter> filters = getFilters(fwRequest);

        if (filters == null || filters.size() == 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest)
                        + (filters == null ? " has no matching filters"
                                : " has an empty filter list"));
            }

            fwRequest.reset();

            chain.doFilter(fwRequest, fwResponse);

            return;
        }

        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        vfc.doFilter(fwRequest, fwResponse);
    }

重點(diǎn)在getFilters(),還記得HttpSecurity創(chuàng)建DefaultSecurityFilterChain的過(guò)程嘛?

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

注意requestMatcher這個(gè)屬性初茶,它被放進(jìn)DefaultSecurityFilterChain對(duì)象中颗祝,也就是說(shuō),requestMatcher同樣存在FilterChainProxy對(duì)象中恼布,getFilters()的原理就是通過(guò)判斷當(dāng)前Request是否和requestMatcher匹配來(lái)決定是否要被spring security的過(guò)濾器鏈攔截

private List<Filter> getFilters(HttpServletRequest request) {
      //注意這個(gè)filterChains螺戳,其實(shí)就是前面securityFilterChainBuilder.build()創(chuàng)建出來(lái)的
        for (SecurityFilterChain chain : filterChains) {
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
        return null;
    }

當(dāng)確定當(dāng)前請(qǐng)求需要被spring security攔截,那么就進(jìn)入VirtualFilterChain.doFilter()方法折汞,也就是上一篇博客Spring security oauth2認(rèn)證流程分析重點(diǎn)分析的spring security各Filter的作用了(ps:只分析了部分Filter的作用)倔幼。所有Filter請(qǐng)參考FilterComparator這個(gè)類。
至此字支,關(guān)于spring security的Filter創(chuàng)建過(guò)程和工作原理基本分析完畢凤藏,小伙伴可以按照博客思路自己重新捋一遍,印象會(huì)更加深刻堕伪。
貼上關(guān)于整個(gè)思路的腦圖:

spring security如何創(chuàng)建Filter及Filter工作原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市栗菜,隨后出現(xiàn)的幾起案子欠雌,更是在濱河造成了極大的恐慌,老刑警劉巖疙筹,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件富俄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡而咆,警方通過(guò)查閱死者的電腦和手機(jī)霍比,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)暴备,“玉大人悠瞬,你說(shuō)我怎么就攤上這事⊙哪恚” “怎么了浅妆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)障癌。 經(jīng)常有香客問(wèn)我凌外,道長(zhǎng),這世上最難降的妖魔是什么涛浙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任康辑,我火速辦了婚禮摄欲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疮薇。我一直安慰自己胸墙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布惦辛。 她就那樣靜靜地躺著劳秋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胖齐。 梳的紋絲不亂的頭發(fā)上玻淑,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音呀伙,去河邊找鬼补履。 笑死,一個(gè)胖子當(dāng)著我的面吹牛剿另,可吹牛的內(nèi)容都是我干的箫锤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雨女,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谚攒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起氛堕,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馏臭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后讼稚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體括儒,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年锐想,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帮寻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赠摇,死狀恐怖固逗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝉稳,我是刑警寧澤抒蚜,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站耘戚,受9級(jí)特大地震影響嗡髓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜收津,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一饿这、第九天 我趴在偏房一處隱蔽的房頂上張望浊伙。 院中可真熱鬧,春花似錦长捧、人聲如沸嚣鄙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哑子。三九已至,卻和暖如春肌割,著一層夾襖步出監(jiān)牢的瞬間卧蜓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工把敞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弥奸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓奋早,卻偏偏與公主長(zhǎng)得像盛霎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耽装,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容