SpringSecurity認證流程源碼詳解

一、認證處理流程說明

原理圖

認證處理流程說明原理圖

1.在前臺輸入完用戶名密碼之后,會進入UsernamePasswordAuthenticationFilter類中去獲取用戶名和密碼,然后去構建一個UsernamePasswordAuthenticationToken對象。
構建一個UsernamePasswordAuthenticationToken對象

這個對象實現了Authentication接口,Authentication接口封裝了驗證信息,在調用UsernamePasswordAuthenticationToken的構造函數的時候先調用父類AbstractAuthenticationToken的構造方法,傳遞一個null,因為在認證的時候并不知道這個用戶有什么權限。之后去給用戶名密碼賦值,最后有一個setAuthenticated(false)方法,代表存進去的信息是否經過了身份認證,源碼如下:
UsernamePasswordAuthenticationToken源碼

2.實例化UsernamePasswordAuthenticationToken之后調用了setDetails(request,authRequest)將請求的信息設到UsernamePasswordAuthenticationToken中去,包括ip、session等內容
setDetails

3.然后去調用AuthenticationManager,AuthenticationManager本身不包含驗證的邏輯,它的作用是用來管理AuthenticationProvider。


5.png

authenticate這個方法是在ProviderManager類上的,這個類實現了AuthenticationManager接口,在authenticate方法中有一個for循環,去拿到所有的AuthenticationProvider,真正校驗的邏輯是寫在AuthenticationProvider中的,為什么是一個集合去進行循環?是因為不同的登陸方式認證邏輯是不一樣的,可能是微信等社交平臺登陸,也可能是用戶名密碼登陸。AuthenticationManager其實是將AuthenticationProvider收集起來,然后登陸的時候挨個去AuthenticationProvider中問你這種驗證邏輯支不支持此次登陸的方式,根據傳進來的Authentication類型會挑出一個適合的provider來進行校驗處理。


6.png

然后去調用provider的驗證方法authenticate方法,authenticate是DaoAuthenticationProvider類中的一個方法,DaoAuthenticationProvider繼承了AbstractUserDetailsAuthenticationProvider。實際上authenticate的校驗邏輯寫在了AbstractUserDetailsAuthenticationProvider抽象類中,首先實例化UserDetails對象,調用了retrieveUser方法獲取到了一個user對象,retrieveUser是一個抽象方法。
7.png

DaoAuthenticationProvider實現了retrieveUser方法,在實現的方法中實例化了UserDetails對象


8.png

也就是相當于自定義驗證邏輯的那個類,去實現UserDetailService類,這個返回結果就是我們自己在數據庫中根據username查詢出來的用戶信息。在AbstractUserDetailsAuthenticationProvider中如果沒拿到信息就會拋出異常,如果查到了就會去調用preAuthenticationChecks的check方法去進行預檢查。
9.png

在預檢查中進行了三個檢查,因為UserDetail類中有四個布爾類型,去檢查其中的三個,用戶是否鎖定、用戶是否過期,用戶是否可用。


10.png

預檢查之后緊接著去調用了additionalAuthenticationChecks方法去進行附加檢查,這個方法也是一個抽象方法,在DaoAuthenticationProvider中去具體實現,在里面進行了加密解密去校驗當前的密碼是否匹配。


11.png

4.如果通過了預檢查和附加檢查,還會進行厚檢查,檢查4個布爾中的最后一個。所有的檢查都通過,則認為用戶認證是成功的。用戶認證成功之后,會將這些認證信息和user傳遞進去,調用createSuccessAuthentication方法.


12.png

在這個方法中同樣會實例化一個user,但是這個方法不會調用之前傳兩個參數的函數,而是會調用三個參數的構造函數。這個時候,在調super的構造函數中不會再傳null,會將authorities權限設進去,之后將用戶密碼設進去,最后setAuthenticated(true),代表驗證已經通過。
13.png

最后創建一個authentication會沿著驗證的這條線返回回去。如果驗證成功,則在這條路中調用我們系統的業務邏輯。如果在任何一處發生問題,就會拋出異常,調用我們自己定義的認證失敗的處理器。

二、認證結果如何在多個請求之間共享

問題:它是什么時候,把什么東西放到了session中,什么時候在session中讀出來。
原理圖:

原理

在驗證成功之后,其中會調用AbstractAuthenticationFilter中的successfulAuthentication方法,在這個方法最后會調用我們自定義的successHandle登陸成功r處理器,在調用這個方法之前會調用SecurityContextHolder.getContext()的setAuthentication方法,會將我們驗證成功的那個Authentication放到SecurityContext中,然后再放到SecurityContextHolder中。SecurityContextImpl中只是重寫了hashcode方法和equals方法去保證Authentication的唯一。
22.png

SecurityContextHolder是ThreadLocal的一個封裝,ThreadLocal是線程綁定的一個map,在同一個線程里在這個方法里往ThreadLocal里設置的變量是可以在另一個線程中讀取到的。它是一個線程級的全局變量,在一個線程中操作ThreadLocal中的數據會影響另一個線程。也就是說創建成功之后,塞進去,此次登陸所有的請求都會通過SecurityContextPersisenceFilter去SecurityContextHolder拿那個Authentication。SecurityContextHolder在整個過濾器的最前面。
23.png

當請求進來的時候,會先經過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對象

其實我只拿到了Principal對象。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容