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對象的詳細信息如圖:

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