Spring Security Servlet 概覽

原文在 GitHub Pages 上,內容無差異spring

Spring Security 是 Spring 框架中用於實現 Security 相關需求的項目。咱們能夠經過使用這個框架來實現項目中的安全需求。segmentfault

今天這篇文章將會討論 Spring Security Servlet 是如何工做的。安全

之因此將內容限定到 Servlet,是由於如今 Spring Security 已經開始支持 Reactive Web Server,由於底層的技術不一樣,固然須要分開討論。架構

Spring Security 在哪裏生效

咱們知道,在 Servlet 中,一次請求會通過這樣的階段: client -> servlet container -> filter -> servletmvc

而 Spring MVC 雖然引入了一些其餘概念,但總體流程差異不大:框架

Spring Security 則是經過實現了 Filter 來實現的 Security 功能。這樣一來,只要使用了 Servlet Container,就可使用 Spring Security,不須要關心有沒有使用 Spring Web 或別的 Spring 項目。ide

DelegatingFilterProxy

這是 Spring Security 實現的一個 Servlet Filter。它被加入到 Servlet Filter Chain 中,將 filter 的任務橋接給 Spring Context 管理的 bean。svg

FilterChainProxy

這是被 DelegatingFilterProxy 封裝的一個 Filter,其實也是一個代理。這個類維護了一個 List<SecurityFilterChain>,它會將請求代理給這個 list 進行 filter 的工做。spa

但這個代理不是遍歷整個 list,而是經過 RequestMatcher 來判斷是否要使用這一個 SecurityFilterChain。咱們配置時寫的 mvcMatchers 之類的方法就會影響到這裏的判斷。線程

SecurityFilterChain

這個接口的實現維護了一個 Filter 列表,這些 Filter 是真正進行 filter 工做的類,好比 CorsFilterUsernamePasswordAuthenticationFilter 等。

上面提到的 RequestMatcher 是這個接口的默認實現使用的。

綜上,咱們能夠獲得一個 big picture:

處理 Security Exception

這裏說的 Security Exception,其實只有兩種:AuthenticationExceptionAccessDeniedException。它們會在 ExceptionTranslationFilter 中被處理,而這個 Filter 每每被安排在 SecurityFilterChain 的最後。

AuthenticationException

這個異常表明身份認證失敗。ExceptionTranslationFilter 會調用 startAuthentication 方法處理它,其流程是:

  1. 清理 SecurityContextHolder 中的身份信息(後面的身份認證內容會涉及)
  2. 將當前請求保存到 RequestCache 中,當用戶經過身份驗證後,會從其中取出當前請求,繼續業務流程
  3. 調用 AuthenticationEntryPoint,要求用戶提供身份信息。方式能夠是重定向到登錄頁面,也能夠是返回攜帶 WWW-Authenticate header 的 HTTP 響應

AccessDeniedException

這個異常表明受權失敗,意味着當前用戶的身份已確認,但被服務拒絕了請求。

ExceptionTranslationFilter 會將這個異常交給 AccessDeniedHanlder 處理。默認的實現會重定向到 /error,並獲得一個 403 響應。


瞭解了 Spring Security 在哪裏生效以後,咱們再來看看兩個重要的問題:身份認證和受權。

身份認證

SecurityContextHolder

SecurityContextHolder 是保存身份信息的地方,默認經過 ThreadLocal 的方式保存 SecurityContext。能夠經過靜態方法 SecurityContextHolder.getSecurityContext() 獲取當前線程的 SecurityContext

SecurityContextHolder.getSecurityContext() 方法雖然是靜態的,能夠在任何地方調用。但我的不建議這麼作,而是應該做爲參數傳遞給使用到的方法,避免當前的 SecurityContext 成爲隱式輸入。

SecurityContext 是一個接口,提供 getAuthentication 方法獲取當前用戶信息;setAuthentication 設置當前用戶信息。

Authentication 也是一個接口,它的實現保存了當前用戶的信息。在身份驗證的流程中,老是在圍繞着 Authentication 操做 —— 經過 PrincipalCredentials 判斷用戶身份、經過調用 setAuthenticated 方法保存身份認證是否經過的結果。

另外,在身份驗證成功後,Authentication 中還保存了 GrantedAuthority 的集合,表示當前用戶的角色和權限,用於後續的受權操做。

AuthenticationManager

AuthenticationManager 提供了 authenticate() 方法用於進行身份驗證,但並非它本身完成,而是經過 AuthenticationProvider 完成。

AuthenticationProvider 提供 support(Authentication) 方法用於判斷是否可以驗證這種類型的 Authentication

AuthenticationManager 的實現 ProviderManager 中保存了 List<AuthenticationProvider>。它會按順序調用支持當前 Authentication 類型的 AuthenticationProviderauthenticate 方法,直到身份驗證成功(返回值 non-null)或所有失敗。

在這個過程當中出現的 AuthenticationException 將會被上面提到的 ExceptionTranslationFilter 處理。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter.doFilter() 方法實現了身份驗證的流程,包括成功和失敗的處理。

它提供了一個抽象方法 attemptAuthentication() 用於身份驗證。子類能夠調用它的 authenticationManager 來實現 authenticate 的功能。

總體流程如圖:

其中的 1 & 2 都在 attemptAuthentication() 方法中完成,須要子類實現。

3 經過 successfulAuthentication() 方法實現,能夠被子類重寫。

4 中除 SessionAuthenticationStrategy 外都交給 unsuccessfulAuthentication() 方法處理,一樣能夠被子類重寫。

考慮到愈來愈多的應用都是基於無狀態的 RESTful API,因此 SessionAuthenticationStrategy 不會在本文涉及

受權

在 Servlet 中受權

Spring Security 受權的入口有不少處,關注到 Servlet 上的話,那就是 FilterSecurityInterceptor 這個 Filter。他會被配置到全部的 AbstractAuthenticationProcessingFilter 子類以後,這樣他就能從 SecurityContextHodler 中獲得 Authentication,用以進行受權。

AccessDecisionManager

受權的過程,被交給 AccessDecisionManager 實現,他的 decide 方法接收三個參數:

  • Authentication:這就是從 SecurityContextHolder 中拿到的對象
  • secureObject:這是一個 Object 類型,對於 FilterSecurityIntercepter 來講,會用 request、response 和 filterChain 建立一個 FilterInvocation 對象做爲 secureObject
  • Collection<ConfigAttribute>FilterSecurityIntercepter 使用 ExpressionBasedFilterInvocationSecurityMetadataSource 保存這些 ConfigAttribute,這些值用來給 AccessDecisionManager 提供作判斷的信息

AccessDecisionManager 天然也不是包含具體的判斷邏輯的角色,真正根據上面三個參數來受權的類,實際上是 AccessDecisionVoter

AccessDecisionVoter

AccessDecisionVoter 提供一個 vote 方法,接收上面的 decide 方法同樣的參數。

他的實現包括 RoleVoterAuthenticationVoter。顧名思義,分別是根據角色和權限信息來判斷是否受權的實現。而_什麼樣的角色/權限能夠訪問這個對象_則是經過 ConfigAttribute 傳入的。

無論具體的 Voter 實現如何,最終會返回一個 int,只有 -一、0、1 三個值,分別表示拒絕、棄權、贊成。

一個 AccessDecisionManager 會管理多個 AccessDecisionVoter,最終會根據全部 voter 的結果來判斷是受權成功,仍是拋出 AccessDeniedException

具體判斷的策略則是交給了 AccessDecisionManager 的三個實現來決定:

ConsensusBased
像通常的比賽投票同樣,票多的結果就是最終決定。
能夠配置票數相等(不是所有棄權)時,結果是否經過,默認值是容許經過。
也能夠配置所有棄權時,結果是否經過,默認值是不容許。

AffirmativeBased
只要有一個 voter 贊成,就容許經過。
一樣能夠配置所有棄權時的決定,默認也是不容許。

UnanimousBased
要求全部 voter 一致贊成時才經過。
一樣能夠配置所有棄權時的決定,默認也是不容許。

AbstractSecurityInterceptor

到此,受權用到的核心類基本介紹完了,讓咱們回過頭來想一個問題:FilterSecurityInterceptor 明明是一個 Filter,爲何要叫作 Interceptor

若是回顧上面介紹的這些類,你會發現只有 FilterSecurityInterceptor 經過實現 Filter 接口和 Servlet 綁定了起來,AccessDecisionManagerAccessDecisionVoter 都沒有和 Servlet 綁定。

這麼作的目的就是爲了能支持 Method Security 和 AspectJ Security,這樣就能複用真正作受權邏輯的代碼。

咱們能夠看到 FilterSecurityInterceptor 擴展了 AbstractSecurityInterceptor。而這個父類的另外兩個實現 MethodSecurityInterceptorAspectJMethodSecurityInterceptor 都是非 Servlet 的實現。由此便作到了對不一樣的受權方式的支持,而且複用了代碼。


關於受權,還有一個很重要的 ACL 沒有提到,它並無影響整個受權的架構,這裏就不寫了,之後有空再說吧。

總結

這篇文章梳理了 Spring Security 在 Servlet 中的代碼架構,構建了一個 big picture。

經過這篇文章,咱們瞭解到,在請求到達真正處理業務的 Controller 以前,經歷了:

  • 各類 AbstractAuthenticationProcessingFilter 過濾請求,交給 AuthenticationManager 管理的 AuthenticationProvider 嘗試不一樣的身份認證方式

    • 最終獲得一個保存在 SecurityContextHolder 中的 Authentication 對象
    • 或者沒法肯定身份的狀況下拋出 AuthenticationException
  • FilterSecurityInterceptor 過濾,使用先前建立的 Authentication 對象交給 AccessDecisionManager 受權

    • 最終成功調用業務方法
    • 或者拋出 AccessDeniedException
  • 上面拋出的 AuthenticationExceptionAccessDeniedException 將會被 ExceptionTranslationFilter 處理,轉化成 401 和 403 的響應。

有了這個 big picture,在接下來研究細節的時候,就不至於摸不着頭腦了。

相關文章
相關標籤/搜索