Spring Security 的權限驗證

原文連接: https://blog.gaoyuexiang.cn/2020/06/13/spring-security-authorization/
內容無差異。

在前面的文章裏,咱們對 Spring Security
進行權限驗證的組件有了大體的瞭解,咱們首先來回顧並探究一下細節。java

本文涉及到的組件

FilterSecurityInterceptor

這是 AbstractSecurityInterceptor 的一個子類,而且實現了 Filter
接口,負責調用父類的 beforeInvocation()afterInvocatio()
finallyInvocation() 方法以及一些 Servlet 相關的工做。
真正處理權限驗證的代碼,其實在父類中。 它存在的意義就是爲了能在 Filter
中進行權限驗證。spring

這個 Filter 默認老是被安排在 SecurityFilterChain
的最後,由於須要保證它在全部的身份認證相關的 Filter 以後。mvc

AbstractSecurityInterceptor

這個類實現了真正的權限驗證的邏輯,它有多個子類,是爲了適配不一樣的技術而存在的,好比上面的
FilterSecurityInterceptor 就是爲了適配 Servlet Filter 而存在的。ide

咱們能夠關注一下上面提到的三個方法,這是每一個子類都會調用的。ui

子類的實現老是下面的套路:spa

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 是一個方法調用,它的類型是 Object,但通常會看到
    MethodInvocation 或者 FilterInvocation 這樣的類型。

beforeInfocation 方法

這個方法的目標是調用 AccessDecisionManager.decide() 方法,完成
pre-invocation handling 操做。代理

在前面的link:/2020/05/31/spring-security-servlet-overview#_權限驗證[概覽]中介紹過,AccessDecisionManager.decide()
方法有三個參數。其中的 secureObject 已經被子類傳進來了。
那麼在真正調用前,就會去獲取 Authentication 對象和
Collection<ConfigAttribute> 集合,而後進行 pre-invocation handling
操做。code

後面會介紹 ConfigAttribute

若是調用時出現 AccessDecisionException,那麼他將會被
ExceptionTranslationFilter 處理。對象

在經過權限驗證以後,就會準備一個 InterceptorStatusToken
對象做爲返回值。blog

在建立 token 以前,會嘗試使用 RunAsManager 建立一個 Authentication
對象,若是這個對象不爲 null,那麼就會把它放入一個
SecurityContext,替換掉 SecurityContextHolder 中原有的那個。

原有的 SecurityContext 老是會被放到 token 中。

關於 RunAsManager :這裏的邏輯是替換掉 SecurityContextHolder 中的值,這樣在目標方法中看到的 Authentication 對象就是這個 RunAsManager 建立的對象。在目標方法調用完成後,即 link:#_finallyinfocation_方法[finallyInvocation 方法] 中,會將原來的 SecurityContext 從新放回 SecurityContextHolder 中。

這樣的目的是爲了將認證與鑑權流程中的 Authentication 對象與業務方法中的區分開來。

在上面的這些步驟中,還會發出一些 ApplicationEvent,包括:
PublicInvocationEventAuthorizationFailureEvent
AuthorizedEvent

PublicInvocationEvent 只在 Collection<ConfigAttribute> 爲空的時候纔會發生,並且這種時候不會調用 AccessDecisionManager

afterInfocation 方法

afterInvocation 方法主要目的是爲了根據 returnedObject
進行權限驗證,這使用到了 AfterInvocationManager
這個接口,這是在概覽裏沒有提到的,它被用來進行
after invocation handling。

在這個方法中,若是有必要的話,就會使用 AfterInvocationManager.decide()
方法來處理 returnedObject,獲得一個新的結果做爲 returntedObject

這裏的有必要是指:

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

finallyInfocation 方法

這個方法接收 InterceptorStatusToken 做爲參數,只作一件事情:將 token
中的 SecurityContext 對象放回 SecurityContextHolder 中。

這個操做有兩個判斷條件:

  • token 不爲 null
  • token 的 contextHolderRefreshRequiredtrue。當
    SecurityContextHolder 中的值在 beforeInvocation
    中被替換時,這個值才爲 true

權限驗證的入口 FilterSecurityInterceptor
的介紹就到這裏,接下來咱們來看看 pre-invocation handling 和 after
invocation handling 的內容,也就是 AccessDecisionManager
AfterInvocationManager

AccessDecisionManager

這是在概覽中介紹過的內容,這裏能夠快速的回顧一下。

AccessDecisionManager 是 pre-invocation handling 的入口。
它的三個具體實現會調用多個 AccessDecisionVoter
的實現,而後具體實現的策略來決定如何根據 voter
的結果來判斷是否經過身份驗證。 每個 voter 都會根據當前的
Authentication 對象、secureObjectCollection<ConfigAttribute>
來作出是否容許訪問的選擇。

AccessDecisionManager 的三個實現,其實就是三種根據 voter
結果來決定最終結果的策略,分別是 AffirmativeBasedConsensusBased
UnanimousBased。策略顧名思義,就不解釋了。

AfterInvocationManager

以前沒有講 after invocation handling
的部分,是以爲不重要,使用場景很少(實際上是本身沒遇到)。如今想講一講,是由於發現
spring-security-acl 使用到了 after invocation handling
的機制。那麼咱們就來看看 AfterInvocationManager 是怎麼工做的。

acl 的部分涉及一些新的概念,準備單獨寫一篇。

經過這個圖,咱們能夠清楚的瞭解到,AfterInvocationManager
也只是一個接口。 它的實現 AfterInvocationProviderManager
則是管理了「不少的」 AfterInvocationProvider
來真正的執行權限驗證的操做。

這裏「不少的」 AfterInvocationProvider 其實也就只有四個個實現,其中三個都是 acl,包括圖裏的這兩個。

剩下的那個 PostInvocationAdviceProvider 其實也沒有真正進行
authorization 操做,而是代理給了 PostInvocationAuthorizationAdvice
處理。 而這個 PostInvocationAuthorizationAdvice 也只有
ExpressionBasedPostInvocationAdvice 這一個實現,也就是基於 SpEL
表達式來進行 authorization 的實現。

而上面提到的全部的 manager 和 provider,都提供了 decide
方法用來作權限驗證。 與 AccessDecisionManager.decide()
相比,這些方法多了一個 returnedObject 參數。
這既是由於它須要做爲判斷條件參與到決策過程當中,也是由於它可能會在決策過程當中被處理,而後返回一個新的
returnedObject 做爲處理後的結果。

ConfigAttribute

這個類是用來存儲咱們的 Security 的配置的。

舉個例子,下面的代碼就會生成相應的 ConfigAttribute

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

上面的代碼定義了:

  • 訪問 /hello 的請求須要具備 test 權限
  • 其餘任意請求,須要經過身份驗證(不容許匿名訪問)

這樣咱們就能獲得這樣的 ConfigAttribute

這是 FilterSecurityInterceptor 的截圖。 其中的
securityMetadataSource 存儲了不少的 ConfigAttribute
AbstractSecurityInterceptor 經過子類實現的
obtainSecurityMetadataSource 方法獲取到它,而後經過它獲取到本次使用的
Collection<ConfigAttribute>

截圖中的 requestMap 保存了 RequestMatcher
Collection<ConfigAttribute> 的關係。

當咱們請求 /hello 時,就會獲得第一個
Collection<ConfigAttribute>,也就是包含了 hasAuthority('test')
的那一個。 當咱們請求其餘接口時,就會獲得第二個。

接着,這些被獲取到的 ConfigAttribute 就能夠被後續的驗證邏輯使用到。

總結

本文介紹了 Spring Security Authorization,並着重介紹了
FilterSecurityInterceptor 如何在 SecurityFilterChain 的最後使用
AccessDecisionManagerAfterInvocationManager 來實現
pre-invocation handling 和 after invocation handling。

對於 AccessDecisionManager
AfterInfocationManager,則沒有詳細介紹內部的邏輯,而是介紹了它們如何利用子類和其餘接口來完成權限驗證的。其內部具體的細節邏輯,讀者能夠本身研究。

相關文章
相關標籤/搜索