Spring-Security登錄認證授權原理

spring-security源碼下載地址:

https://github.com/spring-projects/spring-security

Spring-Security源碼解讀:

1.使用ctrl+shift+n組合鍵查找UsernamePasswordAuthenticationFilter過濾器,該過濾器是用來處理用戶認證邏輯的,進入后如圖:

(1)可以看到它默認的登錄請求url是"/login",并且只允許POST方式的請求

(2)obtainUsername()方法點進去發(fā)現(xiàn)它默認是根據(jù)參數(shù)名為"username"和"password"來獲取用戶名和密碼的

(3)通過構(gòu)造方法實例化一個UsernamePasswordAuthenticationToken對象,此時調(diào)用的是UsernamePasswordAuthenticationToken的兩個參數(shù)的構(gòu)造函數(shù),如圖:

其中super(null)調(diào)用的是父類的構(gòu)造方法,傳入的是權限集合,因為目前還沒有認證通過,所以不知道有什么權限信息,這里設置為null,然后將用戶名和密碼分別賦值給principal和credentials,同樣因為此時還未進行身份認證,所以setAuthenticated(false)

(4)setDetails(request, authRequest)是將當前的請求信息設置到

UsernamePasswordAuthenticationToken中

(5)通過調(diào)用getAuthenticationManager()來獲取AuthenticationManager,通過調(diào)用它的authenticate方法來查找支持該token(UsernamePasswordAuthenticationToken)認證方式的provider,然后調(diào)用該provider的authenticate方法進行認證

2.AuthenticationManager是用來管理AuthenticationProvider的接口,通過查找后進入,然后使用ctrl+H組合鍵查看它的繼承關系,找到ProviderManager實現(xiàn)類,它實現(xiàn)了AuthenticationManager接口,查看它的authenticate方法,它里面有段這樣的代碼:

for(AuthenticationProvider provider:getProviders())

{if(!provider.supports(toTest)){continue;}...

try{result=provider.authenticate(authentication);...

}}

通過for循環(huán)遍歷AuthenticationProvider對象的集合,找到支持當前認證方式的AuthenticationProvider,找到之后調(diào)用該AuthenticationProvider的authenticate方法進行認證處理:

result = provider.authenticate(authentication);

3.AuthenticationProvider接口,就是進行身份認證的接口,它里面有兩個方法:authenticate認證方法和supports是否支持某種類型token的方法,通過ctrl+h查看繼承關系,找到AbstractUserDetailsAuthenticationProvider抽象類,它實現(xiàn)了AuthenticationProvider接口,它的supports方法如下:

public booleansupports(Class<?>authentication){

return(UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));

}

說明它是支持UsernamePasswordAuthenticationToken類型的AuthenticationProvider

再看它的authenticate認證方法,其中有一段這樣的代碼:

用戶信息UserDetails是個接口,我們進入查看,它包含以下6個接口方法:

接著AbstractUserDetailsAuthenticationProvider往下看,找到下面的代碼:

preAuthenticationChecks.check(user);

additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken)authentication);

preAuthenticationChecks預檢查,在最下面的內(nèi)部類DefaultPreAuthenticationChecks中可以看到,它會檢查上面提到的三個boolean方法,即檢查賬戶未鎖定、賬戶可用、賬戶未過期,如果上面的方法只要有一個返回false,就會拋出異常,那么認證就會失敗。

additionalAuthenticationChecks是附加檢查,是個抽象方法,等下看子類的具體實現(xiàn)。

下面還有個postAuthenticationChecks.check(user)后檢查,在最下面的DefaultPostAuthenticationChecks內(nèi)部類中可以看到,它會檢查密碼未過期,如果為false就會拋出異常

如果上面的檢查都通過并且沒有異常,表示認證通過,會調(diào)用下面的方法:

createSuccessAuthentication(principalToReturn, authentication, user);

跟進發(fā)現(xiàn)此時通過構(gòu)造方法實例化對象UsernamePasswordAuthenticationToken時,調(diào)用的是三個參數(shù)的構(gòu)造方法:

publicUsernamePasswordAuthenticationToken(Object principal,Object credentials,Collection<?extendsGrantedAuthority>authorities)

{super(authorities);

this.principal=principal;

this.credentials=credentials;

super.setAuthenticated(true);// must use super, as we override}

此時會調(diào)用父類的構(gòu)造方法設置權限信息,并調(diào)用父類的setAuthenticated(true)方法,到這里就表示認證通過了。

下面我們看看AbstractUserDetailsAuthenticationProvider的子類,同ctrl+h可查看繼承關系,找到DaoAuthenticationProvider

4.DaoAuthenticationProvider類

(1)查看additionalAuthenticationChecks附加檢查方法,它主要是檢查用戶密碼的正確性,如果密碼為空或者錯誤都會拋出異常

(2)獲取用戶信息UserDetails的retrieveUser方法,主要看下面這段代碼:

UserDetails loadedUser=this.getUserDetailsService().loadUserByUsername(username);

它是調(diào)用了getUserDetailsService先獲取到UserDetailsService對象,通過調(diào)用UserDetailsService對象的loadUserByUsername方法獲取用戶信息UserDetails

找到UserDetailsService,發(fā)現(xiàn)它是一個接口,查看繼承關系,有很多實現(xiàn),都是spring-security提供的實現(xiàn)類,并不滿足我們的需要,我們想自己制定獲取用戶信息的邏輯,所以我們可以實現(xiàn)這個接口。比如從我們的數(shù)據(jù)庫中查找用戶信息

5.SecurityContextPersistenceFilter過濾器

那么用戶認證成功之后,又是怎么保存認證信息的呢,在下一次請求過來是如何判斷該用戶是否已經(jīng)認證了呢?

請求進來時會經(jīng)過SecurityContextPersistenceFilter過濾器,進入SecurityContextPersistenceFilter過濾器并找到以下代碼:

SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

從session中獲取SecurityContext對象,如果沒有就實例化一個SecurityContext對象

SecurityContextHolder.setContext(contextBeforeChainExecution);

將SecurityContext對象設置到SecurityContextHolder中

chain.doFilter(holder.getRequest(),holder.getResponse());

表示放行,執(zhí)行下一個過濾器

執(zhí)行完后面的過濾并經(jīng)過servlet處理之后,響應給瀏覽器之前再次經(jīng)過此過濾器。查看以下代碼:

SecurityContext contextAfterChainExecution=SecurityContextHolder.getContext();

SecurityContextHolder.clearContext();

this.repo.saveContext(contextAfterChainExecution,holder.getRequest(),holder.getResponse());

通過SecurityContextHolder獲取SecurityContext對象,然后清除SecurityContext,最后將獲取的SecurityContext對象放入session中

其中SecurityContextHolder是與ThreadLocal綁定的,即本線程內(nèi)所有的方法都可以獲得SecurityContext對象,而SecurityContext對象中包含了Authentication對象,即用戶的認證信息,spring-security判斷用戶是否認證主要是根據(jù)SecurityContext中的Authentication對象來判斷。Authentication對象的詳細信息如圖:

最后整個過程的流程大致如下圖:


?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容