Spring Security 竟然可以同時(shí)存在多個(gè)過濾器鏈囚似?

這是來自一個(gè)小伙伴的提問,我覺得很有必要和大家聊一聊這個(gè)問題:

image

首先這個(gè)問題本身是有點(diǎn)問題的线得,因?yàn)?http.authorizeRequests() 并非總是第一個(gè)饶唤,雖然大部分情況下,我們看到的是第一個(gè)贯钩,但是也有很多情況 http.authorizeRequests() 不是首先出現(xiàn)募狂。要搞明白這個(gè)問題,我們就要搞清楚 http.authorizeRequests() 到底是啥意思角雷!

這就涉及到 Spring Security 中過濾器鏈的配置問題了祸穷,本文松哥就來和大家稍微聊一聊。

本文是 Spring Security 系列第 36 篇勺三,閱讀前面文章有助于更好的理解本文:

  1. 挖一個(gè)大坑雷滚,Spring Security 開搞!
  2. 松哥手把手帶你入門 Spring Security吗坚,別再問密碼怎么解密了
  3. 手把手教你定制 Spring Security 中的表單登錄
  4. Spring Security 做前后端分離祈远,咱就別做頁面跳轉(zhuǎn)了!統(tǒng)統(tǒng) JSON 交互
  5. Spring Security 中的授權(quán)操作原來這么簡單
  6. Spring Security 如何將用戶數(shù)據(jù)存入數(shù)據(jù)庫商源?
  7. Spring Security+Spring Data Jpa 強(qiáng)強(qiáng)聯(lián)手绊含,安全管理只有更簡單!
  8. Spring Boot + Spring Security 實(shí)現(xiàn)自動(dòng)登錄功能
  9. Spring Boot 自動(dòng)登錄炊汹,安全風(fēng)險(xiǎn)要怎么控制躬充?
  10. 在微服務(wù)項(xiàng)目中,Spring Security 比 Shiro 強(qiáng)在哪讨便?
  11. SpringSecurity 自定義認(rèn)證邏輯的兩種方式(高級(jí)玩法)
  12. Spring Security 中如何快速查看登錄用戶 IP 地址等信息充甚?
  13. Spring Security 自動(dòng)踢掉前一個(gè)登錄用戶,一個(gè)配置搞定霸褒!
  14. Spring Boot + Vue 前后端分離項(xiàng)目伴找,如何踢掉已登錄用戶?
  15. Spring Security 自帶防火墻废菱!你都不知道自己的系統(tǒng)有多安全技矮!
  16. 什么是會(huì)話固定攻擊抖誉?Spring Boot 中要如何防御會(huì)話固定攻擊?
  17. 集群化部署衰倦,Spring Security 要如何處理 session 共享袒炉?
  18. 松哥手把手教你在 SpringBoot 中防御 CSRF 攻擊!so easy樊零!
  19. 要學(xué)就學(xué)透徹我磁!Spring Security 中 CSRF 防御源碼解析
  20. Spring Boot 中密碼加密的兩種姿勢(shì)!
  21. Spring Security 要怎么學(xué)驻襟?為什么一定要成體系的學(xué)習(xí)夺艰?
  22. Spring Security 兩種資源放行策略,千萬別用錯(cuò)了沉衣!
  23. 松哥手把手教你入門 Spring Boot + CAS 單點(diǎn)登錄
  24. Spring Boot 實(shí)現(xiàn)單點(diǎn)登錄的第三種方案郁副!
  25. Spring Boot+CAS 單點(diǎn)登錄,如何對(duì)接數(shù)據(jù)庫豌习?
  26. Spring Boot+CAS 默認(rèn)登錄頁面太丑了霞势,怎么辦?
  27. 用 Swagger 測試接口斑鸦,怎么在請(qǐng)求頭中攜帶 Token愕贡?
  28. Spring Boot 中三種跨域場景總結(jié)
  29. Spring Boot 中如何實(shí)現(xiàn) HTTP 認(rèn)證?
  30. Spring Security 中的四種權(quán)限控制方式
  31. Spring Security 多種加密方案共存巷屿,老破舊系統(tǒng)整合利器固以!
  32. 神奇!自己 new 出來的對(duì)象一樣也可以被 Spring 容器管理嘱巾!
  33. Spring Security 配置中的 and 到底該怎么理解憨琳?
  34. 一文搞定 Spring Security 異常處理機(jī)制!
  35. 寫了這么多年代碼旬昭,這樣的登錄方式還是頭一回見篙螟!

1.從過濾器開始

即使大家沒有仔細(xì)研究過 Spring Security 中認(rèn)證、授權(quán)功能的實(shí)現(xiàn)機(jī)制问拘,大概也都多多少少聽說過 Spring Security 這些功能是通過過濾器來實(shí)現(xiàn)的遍略。

是的,沒錯(cuò)骤坐!Spring Security 中一共提供了 32 個(gè)過濾器绪杏,其中默認(rèn)使用的有 15 個(gè),這些過濾器松哥在以后的文章中再和大家細(xì)說纽绍,今天我們就先來看看過濾器的配置問題蕾久。

在一個(gè) Web 項(xiàng)目中,請(qǐng)求流程大概如下圖所示:

image

請(qǐng)求從客戶端發(fā)起(例如瀏覽器)拌夏,然后穿過層層 Filter僧著,最終來到 Servlet 上履因,被 Servlet 所處理。

那有小伙伴要問了盹愚,Spring Security 中默認(rèn)的 15 個(gè)過濾器就是這樣嵌套在 Client 和 Servlet 之間嗎栅迄?

不是的!

上圖中的 Filter 我們可以稱之為 Web Filter杯拐,Spring Security 中的 Filter 我們可以稱之為 Security Filter霞篡,它們之間的關(guān)系如下圖:

image

可以看到世蔗,Spring Security Filter 并不是直接嵌入到 Web Filter 中的端逼,而是通過 FilterChainProxy 來統(tǒng)一管理 Spring Security Filter,F(xiàn)ilterChainProxy 本身則通過 Spring 提供的 DelegatingFilterProxy 代理過濾器嵌入到 Web Filter 之中污淋。

DelegatingFilterProxy 很多小伙伴應(yīng)該比較熟悉顶滩,在 Spring 中手工整合 Spring Session、Shiro 等工具時(shí)都離不開它寸爆,現(xiàn)在用了 Spring Boot礁鲁,很多事情 Spring Boot 幫我們做了,所以有時(shí)候會(huì)感覺 DelegatingFilterProxy 的存在感有所降低赁豆,實(shí)際上它一直都在仅醇。

2.多個(gè)過濾器鏈

上面和大家介紹的是單個(gè)過濾器鏈,實(shí)際上魔种,在 Spring Security 中析二,可能存在多個(gè)過濾器鏈。

在松哥前面講 OAuth2 系列的時(shí)候节预,有涉及到多個(gè)過濾器鏈叶摄,但是一直沒有拎出來單獨(dú)講過,今天就來和大家分享一下安拟。

有人會(huì)問蛤吓,下面這種配置是不是就是多個(gè)過濾器鏈?

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")
            .antMatchers("/user/**").hasRole("user")
            .anyRequest().authenticated()
            ...
            .csrf().disable();
}

這樣的配置相信大家都見過糠赦,但是這并不是多個(gè)過濾器鏈会傲,這是一個(gè)過濾器鏈。因?yàn)椴还苁?/admin/** 還是 /user/** 拙泽,走過的過濾器都是一樣的唆铐,只是不同的路徑判斷條件不一樣而已。

如果系統(tǒng)存在多個(gè)過濾器鏈奔滑,多個(gè)過濾器鏈會(huì)在 FilterChainProxy 中進(jìn)行劃分艾岂,如下圖:

image

可以看到,當(dāng)請(qǐng)求到達(dá) FilterChainProxy 之后朋其,F(xiàn)ilterChainProxy 會(huì)根據(jù)請(qǐng)求的路徑王浴,將請(qǐng)求轉(zhuǎn)發(fā)到不同的 Spring Security Filters 上面去脆炎,不同的 Spring Security Filters 對(duì)應(yīng)了不同的過濾器,也就是不同的請(qǐng)求將經(jīng)過不同的過濾器氓辣。

正常情況下秒裕,我們配置的都是一個(gè)過濾器鏈,多個(gè)過濾器鏈怎么配置呢钞啸?松哥給大家一個(gè)舉一個(gè)簡單的例子:

@Configuration
public class SecurityConfig {
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi").roles("admin", "aaa", "bbb").build());
        manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
        manager.createUser(User.withUsername("江南一點(diǎn)雨").password("{MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2").roles("user", "aaa", "bbb").build());
        return manager;
    }

    @Configuration
    @Order(1)
    static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/foo/**")
                    .authorizeRequests()
                    .anyRequest().hasRole("admin")
                    .and()
                    .csrf().disable();
        }
    }

    @Configuration
    @Order(2)
    static class DefaultWebSecurityConfig2 extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/bar/**")
                    .authorizeRequests()
                    .anyRequest().hasRole("user")
                    .and()
                    .formLogin()
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
}
  1. 首先几蜻,SecurityConfig 不再需要繼承自 WebSecurityConfigurerAdapter 了,只是作為一個(gè)普通的配置類体斩,加上 @Configuration 注解即可梭稚。
  2. 提供 UserDetailsService 實(shí)例,相當(dāng)于是我們的數(shù)據(jù)源絮吵。
  3. 創(chuàng)建靜態(tài)內(nèi)部類繼承 WebSecurityConfigurerAdapter 類弧烤,同時(shí)用 @Configuration 注解標(biāo)記靜態(tài)內(nèi)部類是一個(gè)配置類,配置類里邊的代碼就和之前的一樣了蹬敲,無需贅述暇昂。
  4. 每一個(gè)靜態(tài)內(nèi)部類相當(dāng)于就是一個(gè)過濾器鏈的配置。
  5. 注意在靜態(tài)內(nèi)部類里邊伴嗡,我沒有使用 http.authorizeRequests() 開始急波,http.authorizeRequests() 配置表示該過濾器鏈過濾的路徑是 /**。在靜態(tài)內(nèi)部類里邊瘪校,我是用了 http.antMatcher("/bar/**") 開啟配置澄暮,表示將當(dāng)前過濾器鏈的攔截范圍限定在 /bar/**
  6. 當(dāng)存在多個(gè)過濾器鏈的時(shí)候渣淤,必然會(huì)有一個(gè)優(yōu)先級(jí)的問題赏寇,所以每一個(gè)過濾器鏈的配置類上通過 @Order(2) 注解來標(biāo)記優(yōu)先級(jí)。

從上面這段代碼中大家可以看到价认,configure(HttpSecurity http) 方法似乎就是在配置過濾器鏈嗅定?是的沒錯(cuò)!我們?cè)谠摲椒ㄖ械呐渲糜貌龋际窃谔砑?移除/修改 Spring Security 默認(rèn)提供的過濾器渠退,所以該方法就是在配置 Spring Security 中的過濾器鏈,至于是怎么配置的脐彩,松哥以后抽時(shí)間再來和大家細(xì)說碎乃。

3.回到問題

最后,我們?cè)诨氐揭婚_始小伙伴提的問題惠奸。

首先梅誓,http.authorizeRequests() 配置并非總在第一行出現(xiàn),如果只有一個(gè)過濾器鏈,他總是在第一行出現(xiàn)梗掰,表示該過濾器鏈的攔截規(guī)則是 /**請(qǐng)求只有先被過濾器鏈攔截下來嵌言,接下來才會(huì)進(jìn)入到不同的 Security Filters 中進(jìn)行處理),如果存在多個(gè)過濾器鏈及穗,就不一定了摧茴。

僅僅從字面意思來理解,authorizeRequests() 方法的返回值是 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry埂陆,ExpressionUrlAuthorizationConfigurer 可以為多組不同的 RequestMatcher 配置不同的權(quán)限規(guī)則苛白,就是大家看到的 .antMatchers("/admin/**").hasRole("admin").antMatchers("/user/**").hasRole("user")

4.小結(jié)

好啦焚虱,今天就和小伙伴們簡單分享一下 Spring Security 中過濾器鏈的問題购裙,后面松哥再抽時(shí)間和大家聊一聊過濾器鏈中每一個(gè)過濾器的配置以及含義~公眾號(hào)【江南一點(diǎn)雨】后臺(tái)回復(fù) springsecurity,獲取Spring Security系列 40+ 篇完整文章~

如果小伙伴們覺得有收獲著摔,記得點(diǎn)個(gè)在看鼓勵(lì)下松哥哦~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缓窜,一起剝皮案震驚了整個(gè)濱河市定续,隨后出現(xiàn)的幾起案子谍咆,更是在濱河造成了極大的恐慌,老刑警劉巖私股,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摹察,死亡現(xiàn)場離奇詭異,居然都是意外死亡倡鲸,警方通過查閱死者的電腦和手機(jī)供嚎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭状,“玉大人克滴,你說我怎么就攤上這事∮糯玻” “怎么了劝赔?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胆敞。 經(jīng)常有香客問我着帽,道長,這世上最難降的妖魔是什么移层? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任仍翰,我火速辦了婚禮,結(jié)果婚禮上观话,老公的妹妹穿的比我還像新娘予借。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布灵迫。 她就那樣靜靜地躺著喧笔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪龟再。 梳的紋絲不亂的頭發(fā)上书闸,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音利凑,去河邊找鬼浆劲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哀澈,可吹牛的內(nèi)容都是我干的牌借。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼割按,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼膨报!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起适荣,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤现柠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后弛矛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體够吩,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年丈氓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了周循。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡万俗,死狀恐怖湾笛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闰歪,我是刑警寧澤嚎研,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站课竣,受9級(jí)特大地震影響嘉赎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜于樟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一公条、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧迂曲,春花似錦靶橱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽传黄。三九已至,卻和暖如春队寇,著一層夾襖步出監(jiān)牢的瞬間膘掰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工佳遣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留识埋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓零渐,卻偏偏與公主長得像窒舟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诵盼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355