1. 前言
上一文我們使用 Spring Security 實(shí)現(xiàn)了各種登錄聚合的場(chǎng)面咏尝。其中我們是通過(guò)在 UsernamePasswordAuthenticationFilter
之前一個(gè)自定義的過(guò)濾器實(shí)現(xiàn)的。我怎么知道自定義過(guò)濾器要加在 UsernamePasswordAuthenticationFilter
之前略荡。我在這個(gè)系列開(kāi)篇說(shuō)了 Spring Security 權(quán)限控制的一個(gè)核心關(guān)鍵就是 過(guò)濾器鏈 ,這些過(guò)濾器如下圖進(jìn)行過(guò)濾傳遞蹬叭,甚至比這個(gè)更復(fù)雜!這只是一個(gè)最小單元预明。
Spring Security 內(nèi)置了一些過(guò)濾器,他們各有各的本事耙箍。如果你掌握了這些過(guò)濾器撰糠,很多實(shí)際開(kāi)發(fā)中的需求和問(wèn)題都很容易解決。今天我們來(lái)見(jiàn)識(shí)一下這些內(nèi)置的過(guò)濾器辩昆。
2. 內(nèi)置過(guò)濾器初始化
在 Spring Security 初始化核心過(guò)濾器時(shí) HttpSecurity
會(huì)通過(guò)將 Spring Security 內(nèi)置的一些過(guò)濾器以 FilterComparator
提供的規(guī)則進(jìn)行比較按照比較結(jié)果進(jìn)行排序注冊(cè)阅酪。
2.1 排序規(guī)則
FilterComparator
維護(hù)了一個(gè)順序的注冊(cè)表 filterToOrder
。
FilterComparator() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(ChannelProcessingFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
put(WebAsyncManagerIntegrationFilter.class, order.next());
put(SecurityContextPersistenceFilter.class, order.next());
put(HeaderWriterFilter.class, order.next());
put(CorsFilter.class, order.next());
put(CsrfFilter.class, order.next());
put(LogoutFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
order.next());
filterToOrder.put(
"org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",
order.next());
put(X509AuthenticationFilter.class, order.next());
put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
order.next());
filterToOrder.put(
"org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",
order.next());
put(UsernamePasswordAuthenticationFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
put(DefaultLoginPageGeneratingFilter.class, order.next());
put(DefaultLogoutPageGeneratingFilter.class, order.next());
put(ConcurrentSessionFilter.class, order.next());
put(DigestAuthenticationFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
put(BasicAuthenticationFilter.class, order.next());
put(RequestCacheAwareFilter.class, order.next());
put(SecurityContextHolderAwareRequestFilter.class, order.next());
put(JaasApiIntegrationFilter.class, order.next());
put(RememberMeAuthenticationFilter.class, order.next());
put(AnonymousAuthenticationFilter.class, order.next());
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
order.next());
put(SessionManagementFilter.class, order.next());
put(ExceptionTranslationFilter.class, order.next());
put(FilterSecurityInterceptor.class, order.next());
put(SwitchUserFilter.class, order.next());
}
這些就是所有內(nèi)置的過(guò)濾器汁针。 他們是通過(guò)下面的方法獲取自己的序號(hào):
private Integer getOrder(Class<?> clazz) {
while (clazz != null) {
Integer result = filterToOrder.get(clazz.getName());
if (result != null) {
return result;
}
clazz = clazz.getSuperclass();
}
return null;
}
通過(guò)過(guò)濾器的類(lèi)全限定名從注冊(cè)表 filterToOrder
中獲取自己的序號(hào)术辐,如果沒(méi)有直接獲取到序號(hào)通過(guò)遞歸獲取父類(lèi)在注冊(cè)表中的序號(hào)作為自己的序號(hào),序號(hào)越小優(yōu)先級(jí)越高施无。上面的過(guò)濾器并非全部會(huì)被初始化辉词。有的需要額外引入一些功能包,有的看 HttpSecurity
的配置情況猾骡。 在上一篇文章中瑞躺。我們禁用了 CSRF
功能,就意味著 CsrfFilter
不會(huì)被注冊(cè)兴想。
3. 內(nèi)置過(guò)濾器講解
接下來(lái)我們就對(duì)這些內(nèi)置過(guò)濾器進(jìn)行一個(gè)系統(tǒng)的認(rèn)識(shí)隘蝎。我們將按照默認(rèn)順序進(jìn)行講解。
3.1 ChannelProcessingFilter
ChannelProcessingFilter
通常是用來(lái)過(guò)濾哪些請(qǐng)求必須用 https
協(xié)議襟企, 哪些請(qǐng)求必須用 http
協(xié)議嘱么, 哪些請(qǐng)求隨便用哪個(gè)協(xié)議都行。它主要有兩個(gè)屬性:
ChannelDecisionManager
用來(lái)判斷請(qǐng)求是否符合既定的協(xié)議規(guī)則顽悼。它維護(hù)了一個(gè)ChannelProcessor
列表 這些ChannelProcessor
是具體用來(lái)執(zhí)行ANY_CHANNEL
策略 (任何通道都可以),REQUIRES_SECURE_CHANNEL
策略 (只能通過(guò)https
通道),REQUIRES_INSECURE_CHANNEL
策略 (只能通過(guò)http
通道)曼振。FilterInvocationSecurityMetadataSource
用來(lái)存儲(chǔ) url 與 對(duì)應(yīng)的ANY_CHANNEL
、REQUIRES_SECURE_CHANNEL
蔚龙、REQUIRES_INSECURE_CHANNEL
的映射關(guān)系冰评。
ChannelProcessingFilter
通過(guò) HttpScurity#requiresChannel()
等相關(guān)方法引入其配置對(duì)象 ChannelSecurityConfigurer
來(lái)進(jìn)行配置。
3.2 ConcurrentSessionFilter
ConcurrentSessionFilter
主要用來(lái)判斷session
是否過(guò)期以及更新最新的訪(fǎng)問(wèn)時(shí)間木羹。其流程為:
-
session
檢測(cè)甲雅,如果不存在直接放行去執(zhí)行下一個(gè)過(guò)濾器。存在則進(jìn)行下一步坑填。 - 根據(jù)
sessionid
從SessionRegistry
中獲取SessionInformation
抛人,從SessionInformation
中獲取session
是否過(guò)期;沒(méi)有過(guò)期則更新SessionInformation
中的訪(fǎng)問(wèn)日期脐瑰;
如果過(guò)期妖枚,則執(zhí)行doLogout()
方法,這個(gè)方法會(huì)將session
無(wú)效苍在,并將SecurityContext
中的Authentication
中的權(quán)限置空绝页,同時(shí)在SecurityContenxtHoloder
中清除SecurityContext
然后查看是否有跳轉(zhuǎn)的expiredUrl
荠商,如果有就跳轉(zhuǎn),沒(méi)有就輸出提示信息续誉。
ConcurrentSessionFilter
通過(guò)SessionManagementConfigurer
來(lái)進(jìn)行配置莱没。
3.3 WebAsyncManagerIntegrationFilter
WebAsyncManagerIntegrationFilter
用于集成SecurityContext到Spring異步執(zhí)行機(jī)制中的WebAsyncManager。用來(lái)處理異步請(qǐng)求的安全上下文酷鸦。具體邏輯為:
- 從請(qǐng)求屬性上獲取所綁定的
WebAsyncManager
饰躲,如果尚未綁定,先做綁定井佑。 - 從
asyncManager
中獲取key
為CALLABLE_INTERCEPTOR_KEY
的安全上下文多線(xiàn)程處理器SecurityContextCallableProcessingInterceptor
, 如果獲取到的為null
属铁,
新建一個(gè)SecurityContextCallableProcessingInterceptor
并綁定CALLABLE_INTERCEPTOR_KEY
注冊(cè)到asyncManager
中。
這里簡(jiǎn)單說(shuō)一下 SecurityContextCallableProcessingInterceptor
躬翁。它實(shí)現(xiàn)了接口 CallableProcessingInterceptor
焦蘑,
當(dāng)它被應(yīng)用于一次異步執(zhí)行時(shí),beforeConcurrentHandling()
方法會(huì)在調(diào)用者線(xiàn)程執(zhí)行盒发,該方法會(huì)相應(yīng)地從當(dāng)前線(xiàn)程獲取SecurityContext
,然后被調(diào)用者線(xiàn)程中執(zhí)行邏輯時(shí)例嘱,會(huì)使用這個(gè) SecurityContext
,從而實(shí)現(xiàn)安全上下文從調(diào)用者線(xiàn)程到被調(diào)用者線(xiàn)程的傳輸宁舰。
WebAsyncManagerIntegrationFilter
通過(guò) WebSecurityConfigurerAdapter#getHttp()
方法添加到 HttpSecurity
中成為 DefaultSecurityFilterChain
的一個(gè)鏈節(jié)拼卵。
3.4 SecurityContextPersistenceFilter
SecurityContextPersistenceFilter
主要控制 SecurityContext
的在一次請(qǐng)求中的生命周期 。 請(qǐng)求來(lái)臨時(shí)蛮艰,創(chuàng)建SecurityContext
安全上下文信息腋腮,請(qǐng)求結(jié)束時(shí)清空 SecurityContextHolder
。
SecurityContextPersistenceFilter
通過(guò) HttpScurity#securityContext()
及相關(guān)方法引入其配置對(duì)象 SecurityContextConfigurer
來(lái)進(jìn)行配置壤蚜。
3.5 HeaderWriterFilter
HeaderWriterFilter
用來(lái)給 http
響應(yīng)添加一些 Header
,比如 X-Frame-Options
, X-XSS-Protection
即寡,X-Content-Type-Options
。
你可以通過(guò) HttpScurity#headers()
來(lái)定制請(qǐng)求Header
袜刷。
3.6 CorsFilter
跨域相關(guān)的過(guò)濾器聪富。這是Spring MVC Java
配置和XML
命名空間 CORS
配置的替代方法, 僅對(duì)依賴(lài)于spring-web
的應(yīng)用程序有用(不適用于spring-webmvc
)或 要求在javax.servlet.Filter
級(jí)別進(jìn)行CORS檢查的安全約束鏈接著蟹。這個(gè)是目前官方的一些解讀墩蔓,但是我還是不太清楚實(shí)際機(jī)制。
你可以通過(guò) HttpSecurity#cors()
來(lái)定制萧豆。
3.7 CsrfFilter
CsrfFilter
用于防止csrf
攻擊奸披,前后端使用json交互需要注意的一個(gè)問(wèn)題。
你可以通過(guò) HttpSecurity.csrf()
來(lái)開(kāi)啟或者關(guān)閉它炕横。在你使用 jwt
等 token
技術(shù)時(shí)源内,是不需要這個(gè)的。
3.8 LogoutFilter
LogoutFilter
很明顯這是處理注銷(xiāo)的過(guò)濾器份殿。
你可以通過(guò) HttpSecurity.logout()
來(lái)定制注銷(xiāo)邏輯膜钓,非常有用。
3.9 OAuth2AuthorizationRequestRedirectFilter
和上面的有所不同卿嘲,這個(gè)需要依賴(lài) spring-scurity-oauth2
相關(guān)的模塊颂斜。該過(guò)濾器是處理 OAuth2
請(qǐng)求首選重定向相關(guān)邏輯的。以后會(huì)我會(huì)帶你們認(rèn)識(shí)它拾枣,請(qǐng)多多關(guān)注公眾號(hào):Felordcn
沃疮。
3.10 Saml2WebSsoAuthenticationRequestFilter
這個(gè)需要用到 Spring Security SAML
模塊,這是一個(gè)基于 SMAL
的 SSO
單點(diǎn)登錄請(qǐng)求認(rèn)證過(guò)濾器梅肤。
關(guān)于SAML
SAML
即安全斷言標(biāo)記語(yǔ)言司蔬,英文全稱(chēng)是 Security Assertion Markup Language
。它是一個(gè)基于 XML
的標(biāo)準(zhǔn)姨蝴,用于在不同的安全域(security domain
)之間交換認(rèn)證和授權(quán)數(shù)據(jù)俊啼。在 SAML
標(biāo)準(zhǔn)定義了身份提供者 (identity provider
) 和服務(wù)提供者 (service provider
),這兩者構(gòu)成了前面所說(shuō)的不同的安全域左医。 SAML
是 OASIS
組織安全服務(wù)技術(shù)委員會(huì)(Security Services Technical Committee) 的產(chǎn)品授帕。
SAML
(Security Assertion Markup Language)是一個(gè) XML
框架,也就是一組協(xié)議浮梢,可以用來(lái)傳輸安全聲明跛十。比如,兩臺(tái)遠(yuǎn)程機(jī)器之間要通訊秕硝,為了保證安全芥映,我們可以采用加密等措施,也可以采用 SAML
來(lái)傳輸远豺,傳輸?shù)臄?shù)據(jù)以 XML
形式奈偏,符合 SAML
規(guī)范,這樣我們就可以不要求兩臺(tái)機(jī)器采用什么樣的系統(tǒng)憋飞,只要求能理解 SAML
規(guī)范即可霎苗,顯然比傳統(tǒng)的方式更好。SAML
規(guī)范是一組 Schema
定義榛做。
可以這么說(shuō)唁盏,在Web Service
領(lǐng)域,schema
就是規(guī)范检眯,在 Java
領(lǐng)域厘擂,API
就是規(guī)范
3.11 X509AuthenticationFilter
X509
認(rèn)證過(guò)濾器。你可以通過(guò) HttpSecurity#X509()
來(lái)啟用和配置相關(guān)功能锰瘸。
3.12 AbstractPreAuthenticatedProcessingFilter
AbstractPreAuthenticatedProcessingFilter
處理處理經(jīng)過(guò)預(yù)先認(rèn)證的身份驗(yàn)證請(qǐng)求的過(guò)濾器的基類(lèi)刽严,其中認(rèn)證主體已經(jīng)由外部系統(tǒng)進(jìn)行了身份驗(yàn)證。 目的只是從傳入請(qǐng)求中提取主體上的必要信息避凝,而不是對(duì)它們進(jìn)行身份驗(yàn)證舞萄。
你可以繼承該類(lèi)進(jìn)行具體實(shí)現(xiàn)并通過(guò) HttpSecurity#addFilter
方法來(lái)添加個(gè)性化的AbstractPreAuthenticatedProcessingFilter
眨补。
3.13 CasAuthenticationFilter
CAS
單點(diǎn)登錄認(rèn)證過(guò)濾器 。依賴(lài) Spring Security CAS 模塊
3.14 OAuth2LoginAuthenticationFilter
這個(gè)需要依賴(lài) spring-scurity-oauth2
相關(guān)的模塊倒脓。OAuth2
登錄認(rèn)證過(guò)濾器撑螺。處理通過(guò) OAuth2
進(jìn)行認(rèn)證登錄的邏輯。
3.15 Saml2WebSsoAuthenticationFilter
這個(gè)需要用到 Spring Security SAML
模塊崎弃,這是一個(gè)基于 SMAL
的 SSO
單點(diǎn)登錄認(rèn)證過(guò)濾器甘晤。 關(guān)于SAML
3.16 UsernamePasswordAuthenticationFilter
這個(gè)看過(guò)我相關(guān)文章的應(yīng)該不陌生了。處理用戶(hù)以及密碼認(rèn)證的核心過(guò)濾器饲做。認(rèn)證請(qǐng)求提交的username
和 password
线婚,被封裝成token
進(jìn)行一系列的認(rèn)證,便是主要通過(guò)這個(gè)過(guò)濾器完成的盆均,在表單認(rèn)證的方法中塞弊,這是最最關(guān)鍵的過(guò)濾器。
你可以通過(guò) HttpSecurity#formLogin()
及相關(guān)方法引入其配置對(duì)象 FormLoginConfigurer
來(lái)進(jìn)行配置缀踪。 我們?cè)?Spring Security 實(shí)戰(zhàn)干貨: 玩轉(zhuǎn)自定義登錄 已經(jīng)對(duì)其進(jìn)行過(guò)個(gè)性化的配置和魔改居砖。
3.17 ConcurrentSessionFilter
參見(jiàn) [3.2 ConcurrentSessionFilter](#3.2 ConcurrentSessionFilter) 。 該過(guò)濾器可能會(huì)被多次執(zhí)行驴娃。
3.18 OpenIDAuthenticationFilter
基于OpenID
認(rèn)證協(xié)議的認(rèn)證過(guò)濾器奏候。 你需要在依賴(lài)中依賴(lài)額外的相關(guān)模塊才能啟用它。
3.19 DefaultLoginPageGeneratingFilter
生成默認(rèn)的登錄頁(yè)唇敞。默認(rèn) /login
蔗草。
3.20 DefaultLogoutPageGeneratingFilter
生成默認(rèn)的退出頁(yè)。 默認(rèn) /logout
疆柔。
3.21 ConcurrentSessionFilter
參見(jiàn) [3.2 ConcurrentSessionFilter](#3.2 ConcurrentSessionFilter) 咒精。 該過(guò)濾器可能會(huì)被多次執(zhí)行。
3.23 DigestAuthenticationFilter
Digest
身份驗(yàn)證是 Web
應(yīng)用程序中流行的可選的身份驗(yàn)證機(jī)制 旷档。DigestAuthenticationFilter
能夠處理 HTTP
頭中顯示的摘要式身份驗(yàn)證憑據(jù)模叙。你可以通過(guò) HttpSecurity#addFilter()
來(lái)啟用和配置相關(guān)功能。
3.24 BasicAuthenticationFilter
和Digest
身份驗(yàn)證一樣都是Web
應(yīng)用程序中流行的可選的身份驗(yàn)證機(jī)制 鞋屈。 BasicAuthenticationFilter
負(fù)責(zé)處理 HTTP
頭中顯示的基本身份驗(yàn)證憑據(jù)范咨。這個(gè) Spring Security 的 Spring Boot 自動(dòng)配置默認(rèn)是啟用的 。
BasicAuthenticationFilter
通過(guò) HttpSecurity#httpBasic()
及相關(guān)方法引入其配置對(duì)象 HttpBasicConfigurer
來(lái)進(jìn)行配置厂庇。
3.25 RequestCacheAwareFilter
用于用戶(hù)認(rèn)證成功后渠啊,重新恢復(fù)因?yàn)榈卿洷淮驍嗟恼?qǐng)求。當(dāng)匿名訪(fǎng)問(wèn)一個(gè)需要授權(quán)的資源時(shí)权旷。會(huì)跳轉(zhuǎn)到認(rèn)證處理邏輯替蛉,此時(shí)請(qǐng)求被緩存。在認(rèn)證邏輯處理完畢后,從緩存中獲取最開(kāi)始的資源請(qǐng)求進(jìn)行再次請(qǐng)求躲查。
RequestCacheAwareFilter
通過(guò) HttpScurity#requestCache()
及相關(guān)方法引入其配置對(duì)象 RequestCacheConfigurer
來(lái)進(jìn)行配置它浅。
3.26 SecurityContextHolderAwareRequestFilter
用來(lái) 實(shí)現(xiàn)j2ee
中 Servlet Api
一些接口方法, 比如 getRemoteUser
方法、isUserInRole
方法熙含,在使用 Spring Security 時(shí)其實(shí)就是通過(guò)這個(gè)過(guò)濾器來(lái)實(shí)現(xiàn)的罚缕。
SecurityContextHolderAwareRequestFilter
通過(guò) HttpSecurity.servletApi()
及相關(guān)方法引入其配置對(duì)象 ServletApiConfigurer
來(lái)進(jìn)行配置艇纺。
3.27 JaasApiIntegrationFilter
適用于JAAS
(Java
認(rèn)證授權(quán)服務(wù))怎静。 如果 SecurityContextHolder
中擁有的 Authentication
是一個(gè) JaasAuthenticationToken
,那么該 JaasApiIntegrationFilter
將使用包含在 JaasAuthenticationToken
中的 Subject
繼續(xù)執(zhí)行 FilterChain
黔衡。
3.28 RememberMeAuthenticationFilter
處理 記住我
功能的過(guò)濾器蚓聘。
RememberMeAuthenticationFilter
通過(guò) HttpSecurity.rememberMe()
及相關(guān)方法引入其配置對(duì)象 RememberMeConfigurer
來(lái)進(jìn)行配置。
3.29 AnonymousAuthenticationFilter
匿名認(rèn)證過(guò)濾器盟劫。對(duì)于 Spring Security
來(lái)說(shuō)夜牡,所有對(duì)資源的訪(fǎng)問(wèn)都是有 Authentication
的。對(duì)于無(wú)需登錄(UsernamePasswordAuthenticationFilter
)直接可以訪(fǎng)問(wèn)的資源侣签,會(huì)授予其匿名用戶(hù)身份塘装。
AnonymousAuthenticationFilter
通過(guò) HttpSecurity.anonymous()
及相關(guān)方法引入其配置對(duì)象 AnonymousConfigurer
來(lái)進(jìn)行配置。
3.30 SessionManagementFilter
Session
管理器過(guò)濾器影所,內(nèi)部維護(hù)了一個(gè) SessionAuthenticationStrategy
用于管理 Session
蹦肴。
SessionManagementFilter
通過(guò) HttpScurity#sessionManagement()
及相關(guān)方法引入其配置對(duì)象 SessionManagementConfigurer
來(lái)進(jìn)行配置。
3.31 ExceptionTranslationFilter
主要來(lái)傳輸異常事件猴娩,還記得之前我們見(jiàn)過(guò)的 DefaultAuthenticationEventPublisher
嗎阴幌?
3.32 FilterSecurityInterceptor
這個(gè)過(guò)濾器決定了訪(fǎng)問(wèn)特定路徑應(yīng)該具備的權(quán)限,訪(fǎng)問(wèn)的用戶(hù)的角色卷中,權(quán)限是什么矛双?訪(fǎng)問(wèn)的路徑需要什么樣的角色和權(quán)限?這些判斷和處理都是由該類(lèi)進(jìn)行的蟆豫。如果你要實(shí)現(xiàn)動(dòng)態(tài)權(quán)限控制就必須研究該類(lèi) 议忽。
3.33 SwitchUserFilter
SwitchUserFilter
是用來(lái)做賬戶(hù)切換的。默認(rèn)的切換賬號(hào)的url
為/login/impersonate
十减,默認(rèn)注銷(xiāo)切換賬號(hào)的url
為/logout/impersonate
栈幸,默認(rèn)的賬號(hào)參數(shù)為username
。
你可以通過(guò)此類(lèi)實(shí)現(xiàn)自定義的賬戶(hù)切換嫉称。
4. 總結(jié)
所有內(nèi)置的 31個(gè)過(guò)濾器作用都講解完了侦镇,有一些默認(rèn)已經(jīng)啟用。有一些需要引入特定的包并且對(duì) HttpSecurity
進(jìn)行配置才會(huì)生效 织阅。而且它們的順序是既定的壳繁。 只有你了解這些過(guò)濾器你才能基于業(yè)務(wù)深度定制 Spring Security 。
關(guān)注公眾號(hào):碼農(nóng)小胖哥,獲取更多資訊