SpringSecurity認證流程原始碼詳解
一、認證處理流程說明
原理圖
1.在前臺輸入完使用者名稱密碼之後,會進入UsernamePasswordAuthenticationFilter類中去獲取使用者名稱和密碼,然後去構建一個UsernamePasswordAuthenticationToken物件。
這個物件實現了Authentication介面,Authentication介面封裝了驗證資訊,在呼叫UsernamePasswordAuthenticationToken的建構函式的時候先呼叫父類AbstractAuthenticationToken的構造方法,傳遞一個null,因為在認證的時候並不知道這個使用者有什麼許可權。之後去給使用者名稱密碼賦值,最後有一個setAuthenticated(false)方法,代表存進去的資訊是否經過了身份認證,原始碼如下:
2.例項化UsernamePasswordAuthenticationToken之後呼叫了setDetails(request,authRequest)將請求的資訊設到UsernamePasswordAuthenticationToken中去,包括ip、session等內容
3.然後去呼叫AuthenticationManager,AuthenticationManager本身不包含驗證的邏輯,它的作用是用來管理AuthenticationProvider。
authenticate這個方法是在ProviderManager類上的,這個類實現了AuthenticationManager介面,在authenticate方法中有一個for迴圈,去拿到所有的AuthenticationProvider,真正校驗的邏輯是寫在AuthenticationProvider中的,為什麼是一個集合去進行迴圈?是因為不同的登陸方式認證邏輯是不一樣的,可能是微信等社交平臺登陸,也可能是使用者名稱密碼登陸。AuthenticationManager其實是將AuthenticationProvider收集起來,然後登陸的時候挨個去AuthenticationProvider中問你這種驗證邏輯支不支援此次登陸的方式,根據傳進來的Authentication型別會挑出一個適合的provider來進行校驗處理。
然後去呼叫provider的驗證方法authenticate方法,authenticate是DaoAuthenticationProvider類中的一個方法,DaoAuthenticationProvider繼承了AbstractUserDetailsAuthenticationProvider。實際上authenticate的校驗邏輯寫在了AbstractUserDetailsAuthenticationProvider抽象類中,首先例項化UserDetails物件,呼叫了retrieveUser方法獲取到了一個user物件,retrieveUser是一個抽象方法。
DaoAuthenticationProvider實現了retrieveUser方法,在實現的方法中例項化了UserDetails物件
也就是相當於自定義驗證邏輯的那個類,去實現UserDetailService類,這個返回結果就是我們自己在資料庫中根據username查詢出來的使用者資訊。在AbstractUserDetailsAuthenticationProvider中如果沒拿到資訊就會丟擲異常,如果查到了就會去呼叫preAuthenticationChecks的check方法去進行預檢查。
在預檢查中進行了三個檢查,因為UserDetail類中有四個布林型別,去檢查其中的三個,使用者是否鎖定、使用者是否過期,使用者是否可用。
預檢查之後緊接著去呼叫了additionalAuthenticationChecks方法去進行附加檢查,這個方法也是一個抽象方法,在DaoAuthenticationProvider中去具體實現,在裡面進行了加密解密去校驗當前的密碼是否匹配。
4.如果通過了預檢查和附加檢查,還會進行厚檢查,檢查4個布林中的最後一個。所有的檢查都通過,則認為使用者認證是成功的。使用者認證成功之後,會將這些認證資訊和user傳遞進去,呼叫createSuccessAuthentication方法.
在這個方法中同樣會例項化一個user,但是這個方法不會呼叫之前傳兩個引數的函式,而是會呼叫三個引數的建構函式。這個時候,在調super的建構函式中不會再傳null,會將authorities許可權設進去,之後將使用者密碼設進去,最後setAuthenticated(true),代表驗證已經通過。
最後建立一個authentication會沿著驗證的這條線返回回去。如果驗證成功,則在這條路中呼叫我們系統的業務邏輯。如果在任何一處發生問題,就會丟擲異常,呼叫我們自己定義的認證失敗的處理器。
二、認證結果如何在多個請求之間共享
問題:它是什麼時候,把什麼東西放到了session中,什麼時候在session中讀出來。
原理圖:
在驗證成功之後,其中會呼叫AbstractAuthenticationFilter中的successfulAuthentication方法,在這個方法最後會呼叫我們自定義的successHandle登陸成功r處理器,在呼叫這個方法之前會呼叫SecurityContextHolder.getContext()的setAuthentication方法,會將我們驗證成功的那個Authentication放到SecurityContext中,然後再放到SecurityContextHolder中。SecurityContextImpl中只是重寫了hashcode方法和equals方法去保證Authentication的唯一。
SecurityContextHolder是ThreadLocal的一個封裝,ThreadLocal是執行緒繫結的一個map,在同一個執行緒裡在這個方法裡往ThreadLocal裡設定的變數是可以在另一個執行緒中讀取到的。它是一個執行緒級的全域性變數,在一個執行緒中操作ThreadLocal中的資料會影響另一個執行緒。也就是說建立成功之後,塞進去,此次登陸所有的請求都會通過SecurityContextPersisenceFilter去SecurityContextHolder拿那個Authentication。SecurityContextHolder在整個過濾器的最前面。
當請求進來的時候,會先經過SecurityContextPersisenceFilter,SecurityContextPersisenceFilter會去session中去查SecurityContext的驗證資訊,如果有,就把SecurityContext的驗證資訊放到執行緒裡直接返回回去,如果沒有則通過,去通過其他的過濾器,當請求處理完回來之後,SecurityContextHolder會去檢查當前執行緒中有沒有SecurityContext的驗證資訊,如果有,則將SecurityContext放到session中。通過這樣將不同的請求就可以從同一個session裡拿到驗證資訊。
簡單來說就是進來的時候檢查session,有認證資訊放到執行緒裡。出去的時候檢查執行緒,有認證資訊放到session裡。
因為整個請求和響應的過程都是在一個執行緒裡去完成的,所以線上程的其他位置隨時可以用SecurityContextHolder來拿到認證資訊。
三、獲取認證使用者資訊
其實使用SecurityContextHolder去獲取使用者的認證資訊的。
我在UserController上加入一個新的介面
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/me")
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
然後我在瀏覽器裡先去登入,然後訪問“/user/me”得到使用者的身份資訊
改進
也可以這樣寫
@GetMapping("/me")
public Object getCurrentUser(Authentication authentication){
return authentication;
}
同樣也可以拿到使用者的身份資訊,但是如果我只想拿到使用者名稱不想拿到那麼多一長串怎麼辦?
程式碼可以這樣寫:
@GetMapping("/me")
public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}
然後重新登入訪問:可以看到
其實我只拿到了Principal物件。
相關文章
- SpringSecurity認證和授權流程詳解SpringGse
- SpringSecurity認證流程SpringGse
- 【SpringSecurity系列02】SpringSecurity 表單認證邏輯原始碼解讀SpringGse原始碼
- SpringBoot + Spring Security 學習筆記(二)安全認證流程原始碼詳解Spring Boot筆記原始碼
- SpringSecurity之認證SpringGse
- shiro認證流程原始碼分析--練氣初期原始碼
- SpringSecurity(1)---認證+授權程式碼實現SpringGse
- 登陸認證框架:SpringSecurity框架SpringGse
- 【zookeeper原始碼】啟動流程詳解原始碼
- shiro登陸流程原始碼詳解原始碼
- 二、django rest_framework原始碼之認證流程剖析DjangoRESTFramework原始碼
- Mapreduce Job提交流程原始碼和切片原始碼詳解原始碼
- 微服務整合springsecurity實現認證微服務SpringGse
- SpringSecurity啟動流程原始碼解析 | segmentfault新人第三彈SpringGse原始碼
- JWT身份認證(附帶原始碼講解)JWT原始碼
- Kerberos認證原理詳解ROS
- zxing開源庫工作流程原始碼詳解原始碼
- CB認證流程
- SpringSecurity啟動流程原始碼解析 | 部落格園新人第三彈SpringGse原始碼
- SpringBoot--- 使用SpringSecurity進行授權認證Spring BootGse
- WHQL認證2019年3月 認證流程
- Django模型驗證器詳解和原始碼分析Django模型原始碼
- kerberos 認證流程-理解ROS
- OAuth 2.0 授權認證詳解OAuth
- spring security 認證原始碼跟蹤Spring原始碼
- drf 認證校驗及原始碼分析原始碼
- technology-integration(七)---使用SpringSecurity做JWT認證授權SpringGseJWT
- springsecurity透過策略模式設定統一認證介面SpringGse模式
- SOLIDWORKS認證考試流程Solid
- Django(64)頻率認證原始碼分析與自定義頻率認證Django原始碼
- django-rest-framework原始碼分析2—認證(Authentication)原始碼解析DjangoRESTFramework原始碼
- CSS流程分步程式碼詳解CSS
- 【Hudi】原始碼解讀——Archive 流程原始碼Hive
- ProgressHUD原始碼詳解原始碼
- HashMap原始碼詳解HashMap原始碼
- redux 原始碼詳解Redux原始碼
- TimSort原始碼詳解原始碼
- 【認證與授權】Spring Security系列之認證流程解析Spring