Spring Security 的權(quán)限驗(yàn)證

原文鏈接:https://blog.gaoyuexiang.cn/2020/06/13/spring-security-authorization/
內(nèi)容無(wú)差別。

在前面的文章里幔欧,我們對(duì) Spring Security
進(jìn)行權(quán)限驗(yàn)證的組件有了大致的了解,我們首先來(lái)回顧并探究一下細(xì)節(jié)鹃答。

本文涉及到的組件

FilterSecurityInterceptor

這是 AbstractSecurityInterceptor 的一個(gè)子類,并且實(shí)現(xiàn)了 Filter
接口突硝,負(fù)責(zé)調(diào)用父類的 beforeInvocation()测摔、afterInvocatio()
finallyInvocation() 方法以及一些 Servlet 相關(guān)的工作。
真正處理權(quán)限驗(yàn)證的代碼狞换,其實(shí)在父類中避咆。 它存在的意義就是為了能在 Filter
中進(jìn)行權(quán)限驗(yàn)證舟肉。

這個(gè) Filter 默認(rèn)總是被安排在 SecurityFilterChain
的最后,因?yàn)樾枰WC它在所有的身份認(rèn)證相關(guān)的 Filter 之后查库。

AbstractSecurityInterceptor

這個(gè)類實(shí)現(xiàn)了真正的權(quán)限驗(yàn)證的邏輯路媚,它有多個(gè)子類,是為了適配不同的技術(shù)而存在的樊销,比如上面的
FilterSecurityInterceptor 就是為了適配 Servlet Filter 而存在的整慎。

我們可以關(guān)注一下上面提到的三個(gè)方法,這是每個(gè)子類都會(huì)調(diào)用的围苫。

子類的實(shí)現(xiàn)總是下面的套路:

InterceptorStatusToken token = super.beforeInvocation(secureObject); // 1
try {
  // call target method, eg, filterChain.doFilter()
  // may get a returnedObject
} final {
  super.finallyInvocation(token);
}
super.afterInvocation(token, returnedObject);
  1. secureObject 是一個(gè)方法調(diào)用裤园,它的類型是 Object,但一般會(huì)看到
    MethodInvocation 或者 FilterInvocation 這樣的類型剂府。

beforeInfocation 方法

這個(gè)方法的目標(biāo)是調(diào)用 AccessDecisionManager.decide() 方法拧揽,完成
pre-invocation handling 操作。

在前面的概覽中介紹過(guò)腺占,AccessDecisionManager.decide()
方法有三個(gè)參數(shù)淤袜。其中的 secureObject 已經(jīng)被子類傳進(jìn)來(lái)了。
那么在真正調(diào)用前衰伯,就會(huì)去獲取 Authentication 對(duì)象和
Collection<ConfigAttribute> 集合铡羡,然后進(jìn)行 pre-invocation handling
操作。

后面會(huì)介紹 ConfigAttribute

如果調(diào)用時(shí)出現(xiàn) AccessDecisionException意鲸,那么他將會(huì)被
ExceptionTranslationFilter 處理烦周。

在通過(guò)權(quán)限驗(yàn)證之后,就會(huì)準(zhǔn)備一個(gè) InterceptorStatusToken
對(duì)象作為返回值怎顾。

在創(chuàng)建 token 之前读慎,會(huì)嘗試使用 RunAsManager 創(chuàng)建一個(gè) Authentication
對(duì)象,如果這個(gè)對(duì)象不為 null槐雾,那么就會(huì)把它放入一個(gè)
SecurityContext贪壳,替換掉 SecurityContextHolder 中原有的那個(gè)。

原有的 SecurityContext 總是會(huì)被放到 token 中蚜退。

關(guān)于 RunAsManager :這里的邏輯是替換掉 SecurityContextHolder 中的值,這樣在目標(biāo)方法中看到的 Authentication 對(duì)象就是這個(gè) RunAsManager 創(chuàng)建的對(duì)象彪笼。在目標(biāo)方法調(diào)用完成后钻注,即 finallyInvocation 方法 中,會(huì)將原來(lái)的 SecurityContext 重新放回 SecurityContextHolder 中配猫。

這樣的目的是為了將認(rèn)證與鑒權(quán)流程中的 Authentication 對(duì)象與業(yè)務(wù)方法中的區(qū)分開(kāi)來(lái)幅恋。

在上面的這些步驟中,還會(huì)發(fā)出一些 ApplicationEvent泵肄,包括:
PublicInvocationEvent捆交、AuthorizationFailureEvent
AuthorizedEvent淑翼。

PublicInvocationEvent 只在 Collection<ConfigAttribute> 為空的時(shí)候才會(huì)發(fā)生,而且這種時(shí)候不會(huì)調(diào)用 AccessDecisionManager品追。

afterInfocation 方法

afterInvocation 方法主要目的是為了根據(jù) returnedObject
進(jìn)行權(quán)限驗(yàn)證玄括,這使用到了 AfterInvocationManager
這個(gè)接口,這是在概覽里沒(méi)有提到的肉瓦,它被用來(lái)進(jìn)行
after invocation handling遭京。

在這個(gè)方法中,如果有必要的話泞莉,就會(huì)使用 AfterInvocationManager.decide()
方法來(lái)處理 returnedObject哪雕,得到一個(gè)新的結(jié)果作為 returntedObject

這里的有必要是指:

  1. token != null
  2. afterInvocationManager 字段不為空

finallyInfocation 方法

這個(gè)方法接收 InterceptorStatusToken 作為參數(shù)鲫趁,只做一件事情:將 token
中的 SecurityContext 對(duì)象放回 SecurityContextHolder 中斯嚎。

這個(gè)操作有兩個(gè)判斷條件:

  • token 不為 null

  • token 的 contextHolderRefreshRequiredtrue。當(dāng)
    SecurityContextHolder 中的值在 beforeInvocation
    中被替換時(shí)挨厚,這個(gè)值才為 true


權(quán)限驗(yàn)證的入口 FilterSecurityInterceptor
的介紹就到這里堡僻,接下來(lái)我們來(lái)看看 pre-invocation handling 和 after
invocation handling 的內(nèi)容,也就是 AccessDecisionManager
AfterInvocationManager幽崩。

AccessDecisionManager

這是在概覽中介紹過(guò)的內(nèi)容苦始,這里可以快速的回顧一下。

image

AccessDecisionManager 是 pre-invocation handling 的入口慌申。
它的三個(gè)具體實(shí)現(xiàn)會(huì)調(diào)用多個(gè) AccessDecisionVoter
的實(shí)現(xiàn)陌选,然后具體實(shí)現(xiàn)的策略來(lái)決定如何根據(jù) voter
的結(jié)果來(lái)判斷是否通過(guò)身份驗(yàn)證。 每一個(gè) voter 都會(huì)根據(jù)當(dāng)前的
Authentication 對(duì)象蹄溉、secureObjectCollection<ConfigAttribute>
來(lái)做出是否允許訪問(wèn)的選擇咨油。

AccessDecisionManager 的三個(gè)實(shí)現(xiàn),其實(shí)就是三種根據(jù) voter
結(jié)果來(lái)決定最終結(jié)果的策略柒爵,分別是 AffirmativeBased役电、ConsensusBased
UnanimousBased。策略顧名思義棉胀,就不解釋了法瑟。

AfterInvocationManager

之前沒(méi)有講 after invocation handling
的部分,是覺(jué)得不重要唁奢,使用場(chǎng)景不多(其實(shí)是自己沒(méi)遇到)■現(xiàn)在想講一講,是因?yàn)榘l(fā)現(xiàn)
spring-security-acl 使用到了 after invocation handling
的機(jī)制麻掸。那么我們就來(lái)看看 AfterInvocationManager 是怎么工作的酥夭。

acl 的部分涉及一些新的概念,準(zhǔn)備單獨(dú)寫(xiě)一篇。

image

通過(guò)這個(gè)圖熬北,我們可以清楚的了解到疙描,AfterInvocationManager
也只是一個(gè)接口。 它的實(shí)現(xiàn) AfterInvocationProviderManager
則是管理了“很多的” AfterInvocationProvider
來(lái)真正的執(zhí)行權(quán)限驗(yàn)證的操作讶隐。

這里“很多的” AfterInvocationProvider 其實(shí)也就只有四個(gè)個(gè)實(shí)現(xiàn)起胰,其中三個(gè)都是 acl,包括圖里的這兩個(gè)整份。

剩下的那個(gè) PostInvocationAdviceProvider 其實(shí)也沒(méi)有真正進(jìn)行
authorization 操作待错,而是代理給了 PostInvocationAuthorizationAdvice
處理。 而這個(gè) PostInvocationAuthorizationAdvice 也只有
ExpressionBasedPostInvocationAdvice 這一個(gè)實(shí)現(xiàn)烈评,也就是基于 SpEL
表達(dá)式來(lái)進(jìn)行 authorization 的實(shí)現(xiàn)火俄。

而上面提到的所有的 manager 和 provider,都提供了 decide
方法用來(lái)做權(quán)限驗(yàn)證讲冠。 與 AccessDecisionManager.decide()
相比瓜客,這些方法多了一個(gè) returnedObject 參數(shù)。
這既是因?yàn)樗枰鳛榕袛鄺l件參與到?jīng)Q策過(guò)程中竿开,也是因?yàn)樗赡軙?huì)在決策過(guò)程中被處理谱仪,然后返回一個(gè)新的
returnedObject 作為處理后的結(jié)果。

ConfigAttribute

這個(gè)類是用來(lái)存儲(chǔ)我們的 Security 的配置的否彩。

舉個(gè)例子疯攒,下面的代碼就會(huì)生成相應(yīng)的 ConfigAttribute

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .mvcMatchers("hello")
      .hasAuthority("test")
      .anyRequest()
      .authenticated();
}

上面的代碼定義了:

  • 訪問(wèn) /hello 的請(qǐng)求需要具有 test 權(quán)限

  • 其他任意請(qǐng)求,需要通過(guò)身份驗(yàn)證(不允許匿名訪問(wèn))

這樣我們就能得到這樣的 ConfigAttribute

image

這是 FilterSecurityInterceptor 的截圖列荔。 其中的
securityMetadataSource 存儲(chǔ)了很多的 ConfigAttribute敬尺。
AbstractSecurityInterceptor 通過(guò)子類實(shí)現(xiàn)的
obtainSecurityMetadataSource 方法獲取到它,然后通過(guò)它獲取到本次使用的
Collection<ConfigAttribute>贴浙。

截圖中的 requestMap 保存了 RequestMatcher
Collection<ConfigAttribute> 的關(guān)系砂吞。

當(dāng)我們請(qǐng)求 /hello 時(shí),就會(huì)得到第一個(gè)
Collection<ConfigAttribute>崎溃,也就是包含了 hasAuthority('test')
的那一個(gè)蜻直。 當(dāng)我們請(qǐng)求其他接口時(shí),就會(huì)得到第二個(gè)袁串。

接著概而,這些被獲取到的 ConfigAttribute 就可以被后續(xù)的驗(yàn)證邏輯使用到。

總結(jié)

本文介紹了 Spring Security Authorization囱修,并著重介紹了
FilterSecurityInterceptor 如何在 SecurityFilterChain 的最后使用
AccessDecisionManagerAfterInvocationManager 來(lái)實(shí)現(xiàn)
pre-invocation handling 和 after invocation handling到腥。

對(duì)于 AccessDecisionManager
AfterInfocationManager,則沒(méi)有詳細(xì)介紹內(nèi)部的邏輯蔚袍,而是介紹了它們?nèi)绾卫米宇惡推渌涌趤?lái)完成權(quán)限驗(yàn)證的。其內(nèi)部具體的細(xì)節(jié)邏輯,讀者可以自己研究啤咽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載晋辆,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末宇整,一起剝皮案震驚了整個(gè)濱河市瓶佳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鳞青,老刑警劉巖霸饲,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異臂拓,居然都是意外死亡厚脉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)胶惰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傻工,“玉大人,你說(shuō)我怎么就攤上這事孵滞≈欣Γ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵坊饶,是天一觀的道長(zhǎng)泄伪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)匿级,這世上最難降的妖魔是什么蟋滴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮根蟹,結(jié)果婚禮上脓杉,老公的妹妹穿的比我還像新娘。我一直安慰自己简逮,他們只是感情好球散,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著散庶,像睡著了一般蕉堰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悲龟,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天屋讶,我揣著相機(jī)與錄音,去河邊找鬼须教。 笑死皿渗,一個(gè)胖子當(dāng)著我的面吹牛斩芭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乐疆,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼划乖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挤土?” 一聲冷哼從身側(cè)響起琴庵,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仰美,沒(méi)想到半個(gè)月后迷殿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咖杂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年庆寺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翰苫。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡止邮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奏窑,到底是詐尸還是另有隱情导披,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布埃唯,位于F島的核電站撩匕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏墨叛。R本人自食惡果不足惜止毕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漠趁。 院中可真熱鬧扁凛,春花似錦、人聲如沸闯传。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甥绿。三九已至字币,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間共缕,已是汗流浹背洗出。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留图谷,地道東北人翩活。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓阱洪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親菠镇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子澄峰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355