原文在 GitHub Pages 上,內容無差異spring
Spring Security 是 Spring 框架中用於實現 Security 相關需求的項目。咱們能夠經過使用這個框架來實現項目中的安全需求。segmentfault
今天這篇文章將會討論 Spring Security Servlet 是如何工做的。安全
之因此將內容限定到 Servlet,是由於如今 Spring Security 已經開始支持 Reactive Web Server,由於底層的技術不一樣,固然須要分開討論。架構
咱們知道,在 Servlet 中,一次請求會通過這樣的階段: client -> servlet container -> filter -> servletmvc
而 Spring MVC 雖然引入了一些其餘概念,但總體流程差異不大:框架
Spring Security 則是經過實現了 Filter
來實現的 Security 功能。這樣一來,只要使用了 Servlet Container,就可使用 Spring Security,不須要關心有沒有使用 Spring Web 或別的 Spring 項目。ide
這是 Spring Security 實現的一個 Servlet Filter。它被加入到 Servlet Filter Chain 中,將 filter 的任務橋接給 Spring Context 管理的 bean。svg
這是被 DelegatingFilterProxy
封裝的一個 Filter
,其實也是一個代理。這個類維護了一個 List<SecurityFilterChain>
,它會將請求代理給這個 list 進行 filter 的工做。spa
但這個代理不是遍歷整個 list,而是經過 RequestMatcher
來判斷是否要使用這一個 SecurityFilterChain
。咱們配置時寫的 mvcMatchers
之類的方法就會影響到這裏的判斷。線程
這個接口的實現維護了一個 Filter
列表,這些 Filter
是真正進行 filter 工做的類,好比 CorsFilter
、UsernamePasswordAuthenticationFilter
等。
上面提到的 RequestMatcher
是這個接口的默認實現使用的。
綜上,咱們能夠獲得一個 big picture:
這裏說的 Security Exception,其實只有兩種:AuthenticationException
和 AccessDeniedException
。它們會在 ExceptionTranslationFilter
中被處理,而這個 Filter 每每被安排在 SecurityFilterChain
的最後。
這個異常表明身份認證失敗。ExceptionTranslationFilter
會調用 startAuthentication
方法處理它,其流程是:
SecurityContextHolder
中的身份信息(後面的身份認證內容會涉及)RequestCache
中,當用戶經過身份驗證後,會從其中取出當前請求,繼續業務流程AuthenticationEntryPoint
,要求用戶提供身份信息。方式能夠是重定向到登錄頁面,也能夠是返回攜帶 WWW-Authenticate
header 的 HTTP 響應這個異常表明受權失敗,意味着當前用戶的身份已確認,但被服務拒絕了請求。
ExceptionTranslationFilter
會將這個異常交給 AccessDeniedHanlder
處理。默認的實現會重定向到 /error
,並獲得一個 403 響應。
瞭解了 Spring Security 在哪裏生效以後,咱們再來看看兩個重要的問題:身份認證和受權。
SecurityContextHolder
是保存身份信息的地方,默認經過 ThreadLocal
的方式保存 SecurityContext
。能夠經過靜態方法 SecurityContextHolder.getSecurityContext()
獲取當前線程的 SecurityContext
。
SecurityContextHolder.getSecurityContext()
方法雖然是靜態的,能夠在任何地方調用。但我的不建議這麼作,而是應該做爲參數傳遞給使用到的方法,避免當前的SecurityContext
成爲隱式輸入。
SecurityContext
是一個接口,提供 getAuthentication
方法獲取當前用戶信息;setAuthentication
設置當前用戶信息。
Authentication
也是一個接口,它的實現保存了當前用戶的信息。在身份驗證的流程中,老是在圍繞着 Authentication
操做 —— 經過 Principal
和 Credentials
判斷用戶身份、經過調用 setAuthenticated
方法保存身份認證是否經過的結果。
另外,在身份驗證成功後,Authentication
中還保存了 GrantedAuthority
的集合,表示當前用戶的角色和權限,用於後續的受權操做。
AuthenticationManager
提供了 authenticate()
方法用於進行身份驗證,但並非它本身完成,而是經過 AuthenticationProvider
完成。
AuthenticationProvider
提供 support(Authentication)
方法用於判斷是否可以驗證這種類型的 Authentication
。
在 AuthenticationManager
的實現 ProviderManager
中保存了 List<AuthenticationProvider>
。它會按順序調用支持當前 Authentication
類型的 AuthenticationProvider
的 authenticate
方法,直到身份驗證成功(返回值 non-null)或所有失敗。
在這個過程當中出現的 AuthenticationException
將會被上面提到的 ExceptionTranslationFilter
處理。
AbstractAuthenticationProcessingFilter.doFilter()
方法實現了身份驗證的流程,包括成功和失敗的處理。
它提供了一個抽象方法 attemptAuthentication()
用於身份驗證。子類能夠調用它的 authenticationManager
來實現 authenticate
的功能。
總體流程如圖:
其中的 1
& 2
都在 attemptAuthentication()
方法中完成,須要子類實現。
3
經過 successfulAuthentication()
方法實現,能夠被子類重寫。
4
中除 SessionAuthenticationStrategy
外都交給 unsuccessfulAuthentication()
方法處理,一樣能夠被子類重寫。
考慮到愈來愈多的應用都是基於無狀態的RESTful
API,因此SessionAuthenticationStrategy
不會在本文涉及
Spring Security 受權的入口有不少處,關注到 Servlet 上的話,那就是 FilterSecurityInterceptor
這個 Filter
。他會被配置到全部的 AbstractAuthenticationProcessingFilter
子類以後,這樣他就能從 SecurityContextHodler
中獲得 Authentication
,用以進行受權。
受權的過程,被交給 AccessDecisionManager
實現,他的 decide
方法接收三個參數:
Authentication
:這就是從 SecurityContextHolder
中拿到的對象FilterSecurityIntercepter
來講,會用 request、response 和 filterChain 建立一個 FilterInvocation
對象做爲 secureObjectCollection<ConfigAttribute>
: FilterSecurityIntercepter
使用 ExpressionBasedFilterInvocationSecurityMetadataSource
保存這些 ConfigAttribute
,這些值用來給 AccessDecisionManager
提供作判斷的信息AccessDecisionManager
天然也不是包含具體的判斷邏輯的角色,真正根據上面三個參數來受權的類,實際上是 AccessDecisionVoter
。
AccessDecisionVoter
提供一個 vote
方法,接收上面的 decide
方法同樣的參數。
他的實現包括 RoleVoter
和 AuthenticationVoter
。顧名思義,分別是根據角色和權限信息來判斷是否受權的實現。而_什麼樣的角色/權限能夠訪問這個對象_則是經過 ConfigAttribute
傳入的。
無論具體的 Voter 實現如何,最終會返回一個 int
,只有 -一、0、1 三個值,分別表示拒絕、棄權、贊成。
一個 AccessDecisionManager
會管理多個 AccessDecisionVoter
,最終會根據全部 voter 的結果來判斷是受權成功,仍是拋出 AccessDeniedException
。
具體判斷的策略則是交給了 AccessDecisionManager
的三個實現來決定:
ConsensusBased
像通常的比賽投票同樣,票多的結果就是最終決定。
能夠配置票數相等(不是所有棄權)時,結果是否經過,默認值是容許經過。
也能夠配置所有棄權時,結果是否經過,默認值是不容許。
AffirmativeBased
只要有一個 voter 贊成,就容許經過。
一樣能夠配置所有棄權時的決定,默認也是不容許。
UnanimousBased
要求全部 voter 一致贊成時才經過。
一樣能夠配置所有棄權時的決定,默認也是不容許。
到此,受權用到的核心類基本介紹完了,讓咱們回過頭來想一個問題:FilterSecurityInterceptor
明明是一個 Filter
,爲何要叫作 Interceptor
?
若是回顧上面介紹的這些類,你會發現只有 FilterSecurityInterceptor
經過實現 Filter
接口和 Servlet 綁定了起來,AccessDecisionManager
和 AccessDecisionVoter
都沒有和 Servlet 綁定。
這麼作的目的就是爲了能支持 Method Security 和 AspectJ Security,這樣就能複用真正作受權邏輯的代碼。
咱們能夠看到 FilterSecurityInterceptor
擴展了 AbstractSecurityInterceptor
。而這個父類的另外兩個實現 MethodSecurityInterceptor
和 AspectJMethodSecurityInterceptor
都是非 Servlet 的實現。由此便作到了對不一樣的受權方式的支持,而且複用了代碼。
關於受權,還有一個很重要的 ACL 沒有提到,它並無影響整個受權的架構,這裏就不寫了,之後有空再說吧。
這篇文章梳理了 Spring Security 在 Servlet 中的代碼架構,構建了一個 big picture。
經過這篇文章,咱們瞭解到,在請求到達真正處理業務的 Controller 以前,經歷了:
各類 AbstractAuthenticationProcessingFilter
過濾請求,交給 AuthenticationManager
管理的 AuthenticationProvider
嘗試不一樣的身份認證方式
SecurityContextHolder
中的 Authentication
對象AuthenticationException
被 FilterSecurityInterceptor
過濾,使用先前建立的 Authentication
對象交給 AccessDecisionManager
受權
AccessDeniedException
AuthenticationException
和 AccessDeniedException
將會被 ExceptionTranslationFilter
處理,轉化成 401 和 403 的響應。有了這個 big picture,在接下來研究細節的時候,就不至於摸不着頭腦了。