Spring Security體系結(jié)構(gòu)

? ?

????閱讀該文章前,你需要了解認(rèn)證與授權(quán)的概念,Spring Security就是方便實(shí)現(xiàn)Web應(yīng)用認(rèn)證與授權(quán)的安全管理框架,除此之外你也需要熟悉Servlet Filter的概念與應(yīng)用。
????Spring Security的Servlet支持基于Servlet過(guò)濾器,因此首先了解過(guò)濾器的作用是有幫助的。下圖顯示了單個(gè)HTTP請(qǐng)求的處理程序的典型分層。

????客戶(hù)端向應(yīng)用程序發(fā)送請(qǐng)求,容器根據(jù)請(qǐng)求URI的路徑創(chuàng)建一個(gè)FilterChain,其中包含F(xiàn)ilter實(shí)例和能夠處理HttpServletRequest的Servlet。在Spring MVC應(yīng)用程序中,Servlet是DispatcherServlet的一個(gè)實(shí)例。一個(gè)Servlet最多只能處理一個(gè)HttpServletRequest和HttpServletResponse。但是,可以使用多個(gè)Filter:
防止下游過(guò)濾器實(shí)例或Servlet被調(diào)用。在這種情況下,F(xiàn)ilter通常會(huì)寫(xiě)入HttpServletResponse。
修改下游過(guò)濾器實(shí)例和Servlet使用的HttpServletRequest或HttpServletResponse。
????Filter的功能來(lái)自于傳遞給它的FilterChain。
????由于Filter只影響下游的Filter實(shí)例和Servlet,因此調(diào)用每個(gè)Filter的順序非常重要。
DelegatingFilterProxy
????Spring提供了一個(gè)名為DelegatingFilterProxy的過(guò)濾器實(shí)現(xiàn),它允許在Servlet容器的生命周期和Spring的ApplicationContext之間架橋。Servlet容器允許使用自己的標(biāo)準(zhǔn)注冊(cè)Filter實(shí)例,但它不知道spring定義的bean。您可以通過(guò)標(biāo)準(zhǔn)Servlet容器機(jī)制注冊(cè)DelegatingFilterProxy,但將所有工作委托給實(shí)現(xiàn)Filter的Spring Bean。
????DelegatingFilterProxy的UML類(lèi)圖可以看出來(lái),該類(lèi)實(shí)現(xiàn)了javax.servlet包下的Filter接口,所以為Servlet容器的實(shí)例。

????下面的圖片展示了DelegatingFilterProxy是如何適應(yīng)Filter實(shí)例和FilterChain的。

????DelegatingFilterProxy從ApplicationContext(WebApplicationContext)中查找Bean Filter0,然后調(diào)用Bean Filter0。下面的清單顯示了DelegatingFilterProxy的偽代碼:
(1)惰性獲取已注冊(cè)為Spring Bean的Filter。對(duì)于DelegatingFilterProxy中的例子,委托是Bean Filter0的一個(gè)實(shí)例。
(2)將工作委托給Spring Bean。
????DelegatingFilterProxy的另一個(gè)好處是,它允許延遲查找Filter bean實(shí)例。這很重要,因?yàn)槿萜餍枰谌萜鲉?dòng)之前注冊(cè)Filter實(shí)例。但是,Spring通常使用ContextLoaderListener來(lái)加載Spring bean,直到需要注冊(cè)Filter實(shí)例之后才會(huì)完成。
FilterChainProxy
????Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxy是Spring Security提供的一個(gè)特殊過(guò)濾器,它允許通過(guò)SecurityFilterChain向多個(gè)過(guò)濾器實(shí)例委托。因?yàn)镕ilterChainProxy是一個(gè)Bean,所以它通常被封裝在DelegatingFilterProxy中。
????FilterChainProxy應(yīng)該是在DelegatingFilterProxy的doFilter()方法中調(diào)用,由WebSecurity中的performBuild()方法封裝,performBuild()方法中封裝了SecurityFilterChain接口的實(shí)現(xiàn)過(guò)濾器,SecurityFilterChain為org.springframework.security.web包下的過(guò)濾器,可以理解為spring bean。WebSecurityConfiguration類(lèi)中注冊(cè)名為springSecurityFilterChain的spring bean,返回了WebSecurity bean。
????SecurityFilterChain為一個(gè)接口,該接口的實(shí)現(xiàn)類(lèi)只有一個(gè)不可變類(lèi)DefaultSecurityFilterChain,該類(lèi)的成員屬性private final List<Filter> filters中中封裝了真實(shí)的servlet過(guò)濾器。
????下圖顯示了FilterChainProxy的角色。

????FilterChainProxy使用SecurityFilterChain來(lái)確定應(yīng)該為當(dāng)前請(qǐng)求調(diào)用哪個(gè)Spring SecurityFilter實(shí)例。
????自定義過(guò)濾器并添加到過(guò)濾器鏈中需要自定義配置類(lèi)繼承WebSecurityConfigurerAdapter抽象類(lèi),重寫(xiě)configure(HttpSecurity http)方法,通過(guò)調(diào)用該方法參數(shù)HttpSecurity實(shí)例的addFilterBefore()方法,將自定義過(guò)濾器添加到指定位置。HttpSecurity支持三種filter添加策略:
????HttpSecurity實(shí)例的performBuild()方法構(gòu)造真正的過(guò)濾器鏈,先排序再構(gòu)造過(guò)濾器鏈。HttpSecurity的performBuild()方法在Spring Security應(yīng)用程序啟動(dòng)過(guò)程中執(zhí)行,用于構(gòu)建和配置SecurityFilterChain。
????springSecurityFilterChain
的Filter實(shí)例(上文提到過(guò)WebSecurity)。在Spring Security應(yīng)用程序啟動(dòng)時(shí),Spring容器會(huì)對(duì)所有bean進(jìn)行實(shí)例化和初始化工作,會(huì)執(zhí)行通過(guò)@EnableWebSecurity注釋的spring bean,使用該注釋的配置類(lèi)為繼承WebSecurityConfigurerAdapter抽象類(lèi),執(zhí)行該抽象類(lèi)的init()方法,init()方法調(diào)用final HttpSecurity http = getHttp()方法,該方法返回了一個(gè)HttpSecurity
實(shí)例。在調(diào)用該實(shí)例的其他方法之前,會(huì)隱式地調(diào)用 performBuild()
方法以構(gòu)建和啟用 SecurityFilterChain
。下面是一個(gè)示例 WebSecurityConfigurerAdapter
子類(lèi)中的 configure(HttpSecurity http)
方法的實(shí)現(xiàn),以說(shuō)明 performBuild()
????在上述代碼中,configure(HttpSecurity http)
方法會(huì)在 Spring Security 啟動(dòng)期間調(diào)用,并傳入一個(gè) HttpSecurity
實(shí)例。該方法通過(guò) http
對(duì)象調(diào)用一系列方法來(lái)配置安全策略,如 authorizeRequests()
、formLogin()
、logout()
等。在調(diào)用其他方法之前,performBuild()
方法會(huì)自動(dòng)調(diào)用以構(gòu)建和啟用 SecurityFilterChain
。
????具體實(shí)現(xiàn)步驟為:
????自定義一個(gè)過(guò)濾器debug如下圖所示:

????SecurityFilterChain 接口只有一個(gè)實(shí)現(xiàn)類(lèi),那就是 DefaultSecurityFilterChain。DefaultSecurityFilterChain 其實(shí)就相當(dāng)于是 Spring Security 中的過(guò)濾器鏈,一個(gè) DefaultSecurityFilterChain 代表一個(gè)過(guò)濾器鏈,如果系統(tǒng)中存在多個(gè)過(guò)濾器鏈,則會(huì)存在多個(gè) DefaultSecurityFilterChain 對(duì)象。
????下圖顯示了SecurityFilterChain的角色。

????SecurityFilterChain中的安全過(guò)濾器通常是bean,但它們是在FilterChainProxy而不是DelegatingFilterProxy中注冊(cè)的。FilterChainProxy為直接向Servlet容器或DelegatingFilterProxy注冊(cè)提供了許多優(yōu)點(diǎn)。首先,它為Spring Security的所有Servlet支持提供了一個(gè)起點(diǎn)。因此,如果您試圖對(duì)Spring Security的Servlet支持進(jìn)行故障排除,那么在FilterChainProxy中添加一個(gè)調(diào)試點(diǎn)是一個(gè)很好的開(kāi)始。
????其次,由于FilterChainProxy是Spring Security使用的核心,它可以執(zhí)行非可選的任務(wù)。例如,它清除SecurityContext以避免內(nèi)存泄漏。它還應(yīng)用Spring Security的HttpFirewall來(lái)保護(hù)應(yīng)用程序免受某些類(lèi)型的攻擊。
????此外,它在確定何時(shí)調(diào)用SecurityFilterChain方面提供了更大的靈活性。在Servlet容器中,僅根據(jù)URL調(diào)用Filter實(shí)例。然而,F(xiàn)ilterChainProxy可以通過(guò)使用RequestMatcher接口,基于HttpServletRequest中的任何內(nèi)容來(lái)確定調(diào)用。
????下圖顯示了多個(gè)SecurityFilterChain實(shí)例:

????在Multiple SecurityFilterChain圖中,F(xiàn)ilterChainProxy決定應(yīng)該使用哪個(gè)SecurityFilterChain。只調(diào)用第一個(gè)匹配的SecurityFilterChain。如果請(qǐng)求/api/messages/的URL,它首先匹配/api/**的SecurityFilterChain0模式,因此只調(diào)用SecurityFilterChain0,即使它也匹配SecurityFilterChainn。如果請(qǐng)求/messages/的URL,它與/api/**的SecurityFilterChain0模式不匹配,因此FilterChainProxy繼續(xù)嘗試每個(gè)SecurityFilterChain。假設(shè)沒(méi)有其他SecurityFilterChain實(shí)例匹配,則調(diào)用SecurityFilterChain。
????注意,SecurityFilterChain0只配置了三個(gè)安全過(guò)濾器實(shí)例。然而,securityfilterchain配置了四個(gè)安全過(guò)濾器實(shí)例。需要注意的是,每個(gè)SecurityFilterChain都可以是唯一的,并且可以單獨(dú)配置。事實(shí)上,如果應(yīng)用程序希望Spring security忽略某些請(qǐng)求,SecurityFilterChain可能沒(méi)有安全過(guò)濾器實(shí)例。
Security Filters
????使用SecurityFilterChain API將安全過(guò)濾器插入到FilterChainProxy中。Filter實(shí)例的順序很重要。通常不需要知道Spring Security的Filter實(shí)例的順序。然而,有時(shí)知道順序是有益的。
以下是Spring安全過(guò)濾器排序的綜合列表:
ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
BearerTokenAuthenticationFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
SwitchUserFilter
中途總結(jié)
FilterChainProxy的創(chuàng)建過(guò)程
WebSecurity用來(lái)創(chuàng)建FilterChainProxy過(guò)濾器
框架用法是寫(xiě)一個(gè)自定義配置類(lèi),繼承WebSecurityConfigurerAdapter,重寫(xiě)幾個(gè)configure()方法
WebSecurityConfigurerAdapter就是Web安全配置器的適配器對(duì)象
HttpSecurity用來(lái)創(chuàng)建過(guò)濾器鏈的每個(gè)元素
過(guò)濾器鏈加載過(guò)程
SecurityFilterAutoConfiguration類(lèi)會(huì)加載 DelegatingFilterProxyRegistrationBean注冊(cè)過(guò)濾器,名字為springSecurityFilterChain
DelegatingFilterProxy過(guò)濾器根據(jù)名稱(chēng)springSecurityFilterChain獲得FilterChainProxy過(guò)濾器
FilterChainProxy過(guò)濾器doFilter方法執(zhí)行過(guò)濾器,getFilters方法獲得15個(gè)過(guò)濾器
getFilters方法返回值不為空,創(chuàng)建虛擬過(guò)濾器鏈VirtualFilterChain執(zhí)行過(guò)濾器
Handling Security Exceptions
????ExceptionTranslationFilter允許將AccessDeniedException和AuthenticationException轉(zhuǎn)換為HTTP響應(yīng)。
????ExceptionTranslationFilter作為安全過(guò)濾器之一插入到FilterChainProxy中。
????下圖顯示了ExceptionTranslationFilter與其他組件的關(guān)系:

(1)首先,ExceptionTranslationFilter調(diào)用FilterChain。doFilter(request, response)來(lái)調(diào)用應(yīng)用程序的其余部分。
(2)如果用戶(hù)未經(jīng)過(guò)身份驗(yàn)證或者是AuthenticationException,則啟動(dòng)身份驗(yàn)證。
清除SecurityContextHolder
保存HttpServletRequest,以便在身份驗(yàn)證成功后可以使用它重播原始請(qǐng)求
AuthenticationEntryPoint用于從客戶(hù)端請(qǐng)求憑據(jù)。例如,它可能重定向到登錄頁(yè)面或發(fā)送WWW-Authenticate標(biāo)頭
(3)否則,如果是AccessDeniedException,則AccessDenied。調(diào)用AccessDeniedHandler來(lái)處理拒絕訪問(wèn)。
注意:如果應(yīng)用程序沒(méi)有拋出AccessDeniedException或AuthenticationException,則ExceptionTranslationFilter不做任何事情。
ExceptionTranslationFilter的偽代碼看起來(lái)像這樣:
(1)如過(guò)濾器回顧中所述,調(diào)用FilterChain。doFilter(request, response)相當(dāng)于調(diào)用應(yīng)用程序的其余部分。這意味著如果應(yīng)用程序的另一部分(FilterSecurityInterceptor或方法security)拋出AuthenticationException或AccessDeniedException,它將被捕獲并在這里處理。
(2)如果用戶(hù)未經(jīng)過(guò)身份驗(yàn)證或者是AuthenticationException,則啟動(dòng)身份驗(yàn)證。
(3)否則,拒絕訪問(wèn)。
????如處理安全異常中所示,當(dāng)請(qǐng)求沒(méi)有身份驗(yàn)證并且是針對(duì)需要身份驗(yàn)證的資源時(shí),需要保存已驗(yàn)證資源的請(qǐng)求,以便在身份驗(yàn)證成功后重新請(qǐng)求。在Spring Security中,這是通過(guò)使用RequestCache實(shí)現(xiàn)保存HttpServletRequest來(lái)完成的。
????HttpServletRequest保存在RequestCache中。當(dāng)用戶(hù)成功通過(guò)身份驗(yàn)證時(shí),將使用RequestCache重播原始請(qǐng)求。RequestCacheAwareFilter使用RequestCache來(lái)保存HttpServletRequest。
????默認(rèn)情況下,使用HttpeSsionRequestCache。下面的代碼演示了如何定制RequestCache實(shí)現(xiàn),該實(shí)現(xiàn)用于在參數(shù)continue存在的情況下檢查保存請(qǐng)求的HttpSession。
Prevent the Request From Being Saved
????不希望在會(huì)話中存儲(chǔ)用戶(hù)未經(jīng)身份驗(yàn)證的請(qǐng)求的原因有很多。您可能希望將該存儲(chǔ)空間卸載到用戶(hù)的瀏覽器上,或者將其存儲(chǔ)在數(shù)據(jù)庫(kù)中。或者您可能希望關(guān)閉此功能,因?yàn)槟偸窍M麑⒂脩?hù)重定向到主頁(yè),而不是他們?cè)诘卿浨霸噲D訪問(wèn)的頁(yè)面。
????要做到這一點(diǎn),您可以使用NullRequestCache實(shí)現(xiàn)。
????RequestCacheAwareFilter使用RequestCache來(lái)保存HttpServletRequest。
