原文連接:https://blog.gaoyuexiang.cn/2020/06/07/spring-security-authentication/,內容無差異。java
本文介紹 Spring Security 的身份認證的內容,研究 Spring Security 自帶的身份認證方式和添加本身的身份認證方式的方法。spring
在上一篇文章中,咱們瞭解到了 Spring Security 會將 DelegatingFilterProxy
插入到 Servlet Filter Chain 中,而後將要過濾的請求經過 FilterChainProxy
代理給匹配的 SecurityFilterChain
;這些 SecurityFilterChain
中包含着真正作安全相關工做的 Filter
。數據庫
這些 Filter
中的一部分,他們的職責就是進行身份驗證,好比 UsernamePasswordAuthenticationFilter
;而他們中的大多數都有一個共同的父類 AbstractAuthenticationProcessingFilter
。segmentfault
這個類是不少身份認證的 Filter 的父類,它已經實現了 doFilter
方法,流程以下:安全
本文不涉及其中的 sessionStrategy 部分
doFilter
已經幫咱們搭好了這個流程,咱們只須要關心其中的幾個被調用的方法(紅綠藍三個顏色框)就能夠了。session
這是一個抽象方法。子類實現的時候,須要從 HttpServletRequest
中獲取須要的信息,構建出一個 Authentication
實例,而後調用父類中的 AuthenticationManager.authenticate()
方法,對 Authentication
對象進行認證。mvc
這個方法已經被實現,子類也能夠選擇重寫。根據父類的實現,這個方法將完成一下步驟:ide
SecurityContextHolder
RememberMeService
中的信息(默認使用 NullRememberMeService
)AuthenticationFailureHandler.onAuthenticationFailure()
方法默認使用的 AuthenticationFailureHandler
是 SimpleUrlAuthenticationFailureHandler
,它的邏輯是:spring-boot
若是沒有配置 defaultFailureUrl
(默認沒有)網站
根據配置的布爾值 forwardToDestination
(默認爲 false
)判斷
defaultFailureUrl
defaultFailureUrl
與 unsuccessfulAuthentication
方法同樣,這個方法也已經實現,而且能夠被重寫,但其中的邏輯卻剛好相反:
attemptAuthentication
返回的 Authentication
對象保存到 SecurityContextHolder
中RememberMeService
中InteractiveAuthenticationSuccessEvent
事件,這樣能夠被配置的 EventListener
處理AuthenticationSuccessHandler.onAuthenticationSuccess()
方法默認使用的 AuthenticationSuccessHandler
是 SavedRequestAwareAuthenticationSuccessHandler
,其實現就是一次重定向。咱們能夠看看它重定向到哪裏:
當配置了 alwaysUseDefaultTargetUrl
或指定了 targetUrlParameter
且此參數存在的時候
alwaysUseDefaultTargetUrl
則重定向到 defaultTargetUrl
,默認是 /
targetUrlParameter
(好比 redirect_uri 之類的比較常見的參數),則重定向到這個路徑Referer
,則重定向到這個地址/
從 RequestCache
中找到了保存的請求
關於RequestCache
:想象你正在訪問一個須要認證的資源,這個時候網站會把你重定向到登錄頁面;在你登錄成功後,又會重定向回剛纔的資源。RequestCache
就是爲了保存登錄以前的請求而設計的。在這裏,默認使用基於 session 的實現。
在 AbstractAuthenticationProcessingFilter
中保存了一個 AuthenticationManager
,它會在子類的 attemptAuthentication
方法中被使用。其職責是對 Filter
建立的 Authentication
對象進行身份驗證,好比查詢數據庫匹配用戶名密碼、攜帶的 token 是否合法等。
這是 AuthenticationManager
經常使用的實現。它沒有實現任何認證邏輯,而是管理了一些 AuthenticationProvider
,經過這些 provider 來實現真正的認證功能。
每一個 AuthenticationProvider
實現一種特定類型的身份認證方式,好比用戶名密碼登錄、OIDC 登錄等。他們能夠經過 Authentication
的具體類型來判斷是否支持這種 Authentication
須要的認證方式。
除了 AbstractAuthenticationProcessFilter
,還有一些進行身份驗證的 Filter
,它們並無繼承這個類,而是基於 OncePerRequestFilter
本身實現了一套邏輯。這些 Filter
包括 AuthenticationFilter
、BasicAuthenticationFilter
、OAuth2AuthorizationCodeGrantFilter
等等。
因爲它們再也不是 AbstractAuthenticationProcessFilter
,因此不會再被要求使用 AuthenticationManager
。儘管這樣,當咱們選擇使用 OncePerReuqestFilter
來實現自定義的身份認證時,仍然能夠考慮使用 AuthenticationManager
這種方式。
我的以爲
AuthenticationManager
還算是個不錯的設計,由於作到了職責分離。
甚至還有更加放飛自個人 DigestAuthenticationFilter
,直接繼承 GenericFilterBean
,在實現上也是我行我素,這裏就不探究了。
這個 Filter 不是用來進行身份驗證的,而是用來處理認證受權過程當中產生的異常的。它能夠處理 AuthenticationException
和 AccessDeniedException
,分別表示認證失敗和受權失敗。這篇文章只關心如何處理 AuthenticationException
。
可是這個 Filter
默認被安排在 SecurityFilterChain
的倒數第二位,因此前面的 Filter
拋出的異常並不能被它捕獲。但自定義的 Filter
能夠加到它後面,這樣就能夠利用它來處理這兩種異常。
最後一位是FilterSecurityInterceptor
,可能會拋出AccessDeniedException
。
ExceptionTranslationFilter
對 AuthenticationException
的處理分三步:
SecurityContextHolder
中的身份信息RequestCache
中(用途能夠回顧一下 successfulAuthentication 方法)AuthenticationEntryPoint.commence()
方法其中的 AuthenticationEntryPoint
具體實例取決於你的配置,默認會用到 BasicAuthenticationEntryPoint
。這個接口的職責就是經過 WWW-Authenticate
header 告訴客戶端使用哪一種方式進行身份驗證。
對於 AuthenticationException
和 AccessDeniedException
以外的異常,ExceptionTranslationFilter
會將其轉換成 ServletException
或 RuntimeException
拋出。
若是想要處理這些異常,須要本身添加 Filter
實現。
當咱們啓動 Spring 應用以後,會在日誌裏看到打印全部配置的 FilterChainProxy
。
默認狀況下,咱們會看到這樣的一條鏈:
這是引入 spring-boot-starter-security
以後自動配置的 FilterChainProxy
,在引入更多的 security 相關的依賴和編寫了相關配置以後,這個 filter chain 也會相應變化。
接下來,咱們以 UsernamePawwrodAuthenticationFilter
和 BasicAuthenticationFilter
爲例,看看他們是如何實現身份認證的。
UsernamePasswordAuthenticationFilter
是一個 AbstractAuthenticationProcessingFilter
的子類,實現了 attemptAuthentication
方法,沒有重寫其餘方法。因此用戶認證成功後,會被重定向到一個地址,具體邏輯參考上面的 successfulAuthentication 方法。
attemptAuthentication
方法會從 HttpServletRequest.getParameter()
方法中獲取用戶名密碼,從而進行身份驗證。具體從哪裏獲取用戶名密碼,則能夠被子類經過重寫 obtainUsername()
和 obtainPassword()
方法修改。
以後,UsernamePasswordAuthenticationFilter
會構建出一個 UsernameAuthenticationToken
,交給 AuthenticationManager
進行認證。
這是 UsernamePasswordAuthenticationFilter
對應的 AuthenticationProvider
,負責對 UsernameAuthenticationToken
進行認證。
它使用一個 UserDetailsService
來加載用戶信息,使用 PasswordEncoder
來匹配用戶的密碼。
這兩個接口具體使用哪個實現,取決於具體的配置。好比 UserDetailsService
就有 in memory 和 JDBC 的實現。
UsernamePasswordAuthenticationFilter
是用於單獨處理登陸的Filter
,它不是用來在請求業務 API 時進行身份認證的Filter
。事實上,全部繼承了
AbstractAuthenticationProcessFilter
但沒有重寫successfulAuthentication
方法的Filter
都是這樣的,它們會在登錄成功後重定向到登陸前的地址或默認的地址。這也符合它的語義:進行身份認證流程,而不是業務請求的一部分。
與 UsernamePasswordAuthenticationFilter
不一樣,BasicAuthenticationFilter
沒有繼承 AbstractAuthenticationProcessingFilter
,而是直接繼承 OncePerRequestFilter
。由於它是被使用在請求業務 API 的請求上,而不是進行身份認證流程。
BasicAuthenticationFilter
的實現並不複雜,無非是從 Authorization
header 中取出用戶名密碼,而後建立出 UsernameAuthenticationToken
,接着調用 AuthenticationManager.authenticate()
方法。
之因此它也會使用AuthenticationManager
,應該是出於複用的考慮。這樣它就可使用和UsernamePasswordAuthenticationFilter
同樣的AuthenticationProvider
。
它與 UsernamePasswordAuthenticationFilter
的區別在於認證以後的行爲。
不管認證成功與否,BasicAuthenticationFilter
都不會作出重定向的響應。
BasicAuthenticationEntryPoint
返回 401 響應前面介紹了兩種不一樣的 Filter
實現,以及它們被使用的場景,如今咱們知道了該選擇哪種方式去實現自定義的 Filter
。可是,如何把它們加入到 SecurityFilterChain
中去處理身份認證呢?
咱們若是須要任何對 SecurityFilterChain
的配置,都須要擴展 WebSecurityConfigurerAdapter
,實現本身的一個配置類。每建立這樣的一個實現,都會建立一個 SecurityFilterChain
加入到 FilterChainProxy
中。
咱們在前一篇文章提到過,FilterChainProxy
須要根據 url 來判斷選擇哪個 SecurityFilterChain
。咱們須要將這個配置寫到這個實現類中,好比:
@Override protected void configure(HttpSecurity http) throws Exception { http.requestMatchers(matcher -> matcher.mvcMatchers("/hello")); }
這樣,FilterChainProxy
就知道了對 /hello
的請求須要使用這個 SecurityFilterChain
。
如今有了對應的 SecurityFilterChain
,咱們就能夠將自定義的 Filter
加入到這個 chain 中:
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilter(HelloFilter.class); }
addFilter
方法也有一些變體,能夠控制 Filter
在 chain 中的位置,這裏就不贅述了。
與 Filter
同樣,AuthenticationProvider
也是被安排到單獨的 FilterChainProxy
中的,而且須要本身配置。若是你的自定義 Filter
須要 AuthenticationProvider
的話,一樣須要配置:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(HelloAuthenticationProvider.class); }
這篇文章比較詳細的梳理了 AbstractAuthenticationProcessingFilter
及其子類 UsernamePasswordAuthenticationFilter
的實現和 BasicAuthenticationFilter
的實現,瞭解了須要實現自定義身份驗證的 Filter
時應該選擇哪一種方式:
AbstractAuthenticationProcessFilter
OncePerRequestFilter
,徹底控制認證的流程
固然,這不是一個強制的限制,你仍然能夠經過重寫
AbstractAuthenticationProcessFilter.successfulAuthentication()
方法來修改重定向的行爲。
另外,也瞭解到了實現完 Filter
後,須要實現 WebSecurityConfigurerAdapter
,將 Filter
加入到 SecurityFilterChain
中。
👉 查看系列文章