在前面的六章中,介紹了 Spring Security 的基礎使用,在繼續深入向下的學習前,有必要理解清楚 Spring Security 的認證流程,這樣才能理解爲什麼要這樣寫代碼,也方便後續的擴展。
一、認證流程
上圖是 Spring Security 認證流程的一部分,下面的講解以上圖爲依據。
(1) 用戶發起表單登錄請求後,首先進入 UsernamePasswordAuthenticationFilter
:
在 UsernamePasswordAuthenticationFilter 中根據用戶輸入的用戶名、密碼構建了 UsernamePasswordAuthenticationToken
,並將其交給 AuthenticationManager 來進行認證處理。
AuthenticationManager 本身不包含認證邏輯,其核心是用來管理所有的 AuthenticationProvider
,通過交由合適的 AuthenticationProvider 來實現認證。
(2) 下面跳轉到了 ProviderManager
,該類是 AuthenticationManager 的實現類:
我們知道不同的登錄邏輯它的認證方式是不一樣的,比如我們表單登錄需要認證用戶名和密碼,但是當我們使用三方登錄時就不需要驗證密碼。
Spring Security 支持多種認證邏輯,每一種認證邏輯的認證方式其實就是一種 AuthenticationProvider。通過 getProviders()
方法就能獲取所有的 AuthenticationProvider,通過 provider.supports()
來判斷 provider 是否支持當前的認證邏輯。
當選擇好一個合適的 AuthenticationProvider 後,通過 provider.authenticate(authentication)
來讓 AuthenticationProvider 進行認證。
(3) 傳統表單登錄的 AuthenticationProvider 主要是由 AbstractUserDetailsAuthenticationProvider
來進行處理的,我們來看下它的 authenticate()
方法。
首先通過 retrieveUser()
方法讀取到數據庫中的用戶信息:
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
- 1
retrieveUser() 的具體實現在 DaoAuthenticationProvider
中,代碼如下:
當我們成功的讀取 UserDetails
後,下面開始對其進行認證:
在上圖中,我們可以看到認證校驗分爲 前校驗、附加校驗和後校驗,如果任何一個校驗出錯,就會拋出相應的異常。所有校驗都通過後,調用 createSuccessAuthentication()
返回認證信息。
在 createSuccessAuthentication 方法中,我們發現它重新 new 了一個 UsernamePasswordAuthenticationToken
,因爲到這裏認證已經通過了,所以將 authorities 注入進去,並設置 authenticated 爲 true,即需要認證。
(4)至此認證信息就被傳遞迴 UsernamePasswordAuthenticationFilter 中,在 UsernamePasswordAuthenticationFilter 的父類 AbstractAuthenticationProcessingFilter
的 doFilter()
中,會根據認證的成功或者失敗調用相應的 handler:
這裏調用的 handler 實際就是在《SpringBoot集成Spring Security(6)——登錄管理》中我們在配置文件中配置的 successHandler()
和 failureHandler()
。
二、多個請求共享認證信息
Spring Security 通過 Session
來保存用戶的認證信息,那麼 Spring Security 到底是在什麼時候將認證信息放入 Session,又在什麼時候將認證信息從 Session 中取出來的呢?
下面將 Spring Security 的認證流程補充完整,如下圖:
在上一節認證成功的 successfulAuthentication()
方法中,有一行語句:
SecurityContextHolder.getContext().setAuthentication(authResult);
- 1
其實就是在這裏將認證信息放入 Session 中。
查看 SecurityContext
源碼,發現內部就是對 Authentication 的封裝,提供了 equals、hashcode、toString等方法,而SecurityContextHolder
可以理解爲線程中的 ThreadLocal
。
我們知道一個 HTTP 請求和響應都是在一個線程中執行,因此在整個處理的任何一個方法中都可以通過 SecurityContextHolder.getContext()
來取得存放進去的認證信息。
從 Session 中對認證信息的處理由 SecurityContextPersistenceFilter
來處理,它位於 Spring Security 過濾器鏈的最前面,它的主要作用是:
- 當請求時,檢查 Session 中是否存在 SecurityContext,如果有將其放入到線程中。
- 當響應時,檢查線程中是否存在 SecurityContext,如果有將其放入到 Session 中。
三、獲取用戶認證信息
通過調用 SecurityContextHolder.getContext().getAuthentication()
就能夠取得認證信息:
@GetMapping("/me")
@ResponseBody
public Object me() {
return SecurityContextHolder.getContext().getAuthentication();
}
- 1
- 2
- 3
- 4
- 5
上面的寫法有點囉嗦,我們可以簡寫成下面這種, Spring MVC 會自動幫我們從 Spring Security 中注入:
@GetMapping("/me") @ResponseBody public Object me(Authentication authentication) { return authentication; }
如果你僅想獲取
UserDetails
對象,也是可以的,寫法如下:@GetMapping("/me") @ResponseBody public Object me(@AuthenticationPrincipal UserDetails userDetails) { return userDetails; }
整合了密碼、短信驗證碼、qq、人臉登陸:https://github.com/jitwxs/express