Spring Security 認證執行流程


本文基於 Spring Security 5.x

推薦閱讀:

項目集成Spring Security

SpringSecurity 整合 JWT

一、外層-正常登陸調用

項目啟動后會自動尋找 UserDetailsService 實現類;

執行 UserDetailsService 的唯一方法 loadUserByName(String username) 並返回 UserDetail 類,注意,返回的 UserDetail 是根據用戶名去數據庫查詢到用戶信息;

拿到 UserDetail 后會對 UserDetail 進行一個預檢查;

預檢查啥?

用戶是否存在,是否被鎖定等等等;

全部認證成功后會調用 AuthenticationSuccess 成功處理類,失敗則調用 AuthenticationFailHandler 類;

此時對於前后端分離項目而言,調用成功處理類,通常是返回由 JWT 等生成的 token json 字符串,前台拿到返回信息后,保存 token 致本地,然后每次請求都會拼接到 head 中。

二、內層-源碼級別

以訪問某個項目中已有的鏈接為例:

http://localhost:7777/tmax/videoCategory/getAll

輸入用戶名、密碼后點擊登錄按鈕,首先進入 UsernamePassworkAuthenticationFilter 的父類
AbstractAuthenticationProcessingFilter 調用 doFilter() 方法,然后再執行 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法進行驗證;

UsernamePassworkAuthenticationFilter 類,顧名思義,表單登陸過濾器,該類中重點是 attemptAuthentication() 方法:

該方法中通過 用戶名+密碼= 實例化一個 UsernamePasswordAuthenticationToken 的對象,作用是將用戶請求的信息(用戶名、密碼、seeesion等)封裝到該對象中,我們點擊進入該對象的構造器如下圖所示:

需要說明一點的是,super((Collection)null); collection 代表權限列表,在這傳了一個 null 進去是因為剛開始並沒有進行認證,因此用戶此時沒有任何權限,並且設置沒有認證的信息 setAuthenticated(false)

再回到 UsernamePassworkAuthenticationFilter attemptAuthentication() 方法,可以看到方法最后調用了 getAuthenticationManager() 方法,然后就進入了 AuthenticationManager 接口的實現類 ProviderManager 中。

補充:AuthenticationManager 不包含驗證用戶名以及密碼的功能,只是用來管理 AuthenticationProvider,所有的校驗規則都是寫在 AuthenticationProvider 中的;

繼續走,在 ProviderManager 這個實現類中,它會調用AuthenticationProvider 接口的實現類獲取用戶的信息,用戶的信息權限的驗證就在該類中校驗。

進入 ProviderManager 類后會調用 authenticate(Authentication authentication) 方法,它通過 AuthenticationProvider 實現類獲取用戶的登錄的方式,然后會有一個 while 迭代器模式的循環遍歷,檢查它是否支持這種登錄方式,具體的登錄方式有表單登錄,qq登錄,微信登錄等。如果最終都不支持會拋出相應的異常信息,如果支持則會進入AuthenticationProvider 接口的抽象實現類 AbstractUserDetailsAuthenticationProvider 中。

進入 AbstractUserDetailsAuthenticationProvider 類后會調用 authenticate(Authentication authentication) 方法對用戶的身份進行校驗,首先是判斷用戶是否為空,這個 user 是 UserDetail 的對象,如果為空,表示還沒有認證,就需要調用 retrieveUser 方法去獲取用戶的信息,這個方法是抽象類 AbstractUserDetailsAuthenticationProvider 的擴展類DaoAuthenticationProvider 的一個方法。

在該擴展類的 retrieveUser 方法中調用 UserDetailsService 這個接口的實現類的 loadUserByUsername 方法去獲取用戶信息,而這里我自己編寫了實現類 UserDetailsServiceImpl 類,在這個實現類中,我們可以編寫自己的邏輯,從數據庫中獲取用戶密碼等權限信息返回。

本地 UserDetailService 實現類 UserDetailsServiceImpl:

在拿到用戶的信息后,返回到 AbstractUserDetailsAuthenticationProvider 類中調用 createSuccessAuthentication(principalToReturn, authentication, user) 方法,在該方法中會調用三個參數的UsernamePasswordAuthenticationToken 構造器,不同於前面調用兩個參數的,因為這里已經驗證了用戶的信息和權限,因此不再是給父類構造器中傳null 值了,而是用戶的權限集合,並且設置認證通過setAuthenticated(true)

如下是 UsernamePasswordAuthenticationToken 構造器:

此時 authorities 不再為空了。

在 UsernamePasswordAuthenticationToken 的父類中,它會檢查用的權限,如果有一個為 null,表示權限沒有相應的權限,拋出異常。

然后在 createSuccessAuthentication 方法返回后回到 ProvioderManager 的 authenticate 方法中返回 result,最后回到UsernamePasswordAuthenticationFilter 的剛開始進入的 attemptAuthentication 方法中返回。

attemptAuthentication() 方法中的返回,返回到哪?

再回到第一張圖,UsernamePasswordAuthenticationFilter 父類 doFilter() 方法,返回值就是 authResult,如果過程中發現存在異常則執行 unsuccessfulAuthentication.onAuthenticationFailure() 方法,如果認證成功則執行 successfulAuthentication.onAuthenticationSuccess() 方法,再結合上邊提到的自定義 成功/失敗處理類。

最后總結

流程大致是,首先進入 UsernamePasswordAuthenticationFilter 父類 AbstractAuthenticationProcessingFilter 執行 doFilter() 方法,這個 doFilter() 方法呢執行如下:

  1. 判斷當前的 filter 是否可以處理當前請求,不可以的話則交給下一個 filter 處理;
  2. 抽象方法由子類 UsernamePasswordAuthenticationFilter 實現;
  3. 認證成功后,調用 sessionStrategy.onAuthentication() 處理一些與 session 相關的方法,最后再調用認證成功處理類,主要將當前的認證放到SecurityContextHolder中;
  4. 認證失敗后則調用 認證失敗處理類;

習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:niceyoo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM