spring-security源碼下載地址:
https://github.com/spring-projects/spring-security
Spring-Security源碼解讀:
1.使用ctrl+shift+n組合鍵查找UsernamePasswordAuthenticationFilter過(guò)濾器,該過(guò)濾器是用來(lái)處理用戶(hù)認(rèn)證邏輯的墓阀,進(jìn)入后如圖:
(1)可以看到它默認(rèn)的登錄請(qǐng)求url是"/login"庄新,并且只允許POST方式的請(qǐng)求
(2)obtainUsername()方法點(diǎn)進(jìn)去發(fā)現(xiàn)它默認(rèn)是根據(jù)參數(shù)名為"username"和"password"來(lái)獲取用戶(hù)名和密碼的
(3)通過(guò)構(gòu)造方法實(shí)例化一個(gè)UsernamePasswordAuthenticationToken對(duì)象,此時(shí)調(diào)用的是UsernamePasswordAuthenticationToken的兩個(gè)參數(shù)的構(gòu)造函數(shù),如圖:
其中super(null)調(diào)用的是父類(lèi)的構(gòu)造方法烹看,傳入的是權(quán)限集合咨跌,因?yàn)槟壳斑€沒(méi)有認(rèn)證通過(guò)庆锦,所以不知道有什么權(quán)限信息,這里設(shè)置為null,然后將用戶(hù)名和密碼分別賦值給principal和credentials腔召,同樣因?yàn)榇藭r(shí)還未進(jìn)行身份認(rèn)證杆查,所以setAuthenticated(false)
(4)setDetails(request, authRequest)是將當(dāng)前的請(qǐng)求信息設(shè)置到
UsernamePasswordAuthenticationToken中
(5)通過(guò)調(diào)用getAuthenticationManager()來(lái)獲取AuthenticationManager,通過(guò)調(diào)用它的authenticate方法來(lái)查找支持該token(UsernamePasswordAuthenticationToken)認(rèn)證方式的provider臀蛛,然后調(diào)用該provider的authenticate方法進(jìn)行認(rèn)證
2.AuthenticationManager是用來(lái)管理AuthenticationProvider的接口亲桦,通過(guò)查找后進(jìn)入,然后使用ctrl+H組合鍵查看它的繼承關(guān)系浊仆,找到ProviderManager實(shí)現(xiàn)類(lèi)烙肺,它實(shí)現(xiàn)了AuthenticationManager接口,查看它的authenticate方法氧卧,它里面有段這樣的代碼:
for(AuthenticationProvider provider:getProviders())
{if(!provider.supports(toTest)){continue;}...
try{result=provider.authenticate(authentication);...
}}
通過(guò)for循環(huán)遍歷AuthenticationProvider對(duì)象的集合桃笙,找到支持當(dāng)前認(rèn)證方式的AuthenticationProvider,找到之后調(diào)用該AuthenticationProvider的authenticate方法進(jìn)行認(rèn)證處理:
result = provider.authenticate(authentication);
3.AuthenticationProvider接口沙绝,就是進(jìn)行身份認(rèn)證的接口搏明,它里面有兩個(gè)方法:authenticate認(rèn)證方法和supports是否支持某種類(lèi)型token的方法鼠锈,通過(guò)ctrl+h查看繼承關(guān)系,找到AbstractUserDetailsAuthenticationProvider抽象類(lèi)星著,它實(shí)現(xiàn)了AuthenticationProvider接口购笆,它的supports方法如下:
public booleansupports(Class<?>authentication){
return(UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
說(shuō)明它是支持UsernamePasswordAuthenticationToken類(lèi)型的AuthenticationProvider
再看它的authenticate認(rèn)證方法,其中有一段這樣的代碼:
用戶(hù)信息UserDetails是個(gè)接口虚循,我們進(jìn)入查看同欠,它包含以下6個(gè)接口方法:
接著AbstractUserDetailsAuthenticationProvider往下看,找到下面的代碼:
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken)authentication);
preAuthenticationChecks預(yù)檢查横缔,在最下面的內(nèi)部類(lèi)DefaultPreAuthenticationChecks中可以看到铺遂,它會(huì)檢查上面提到的三個(gè)boolean方法,即檢查賬戶(hù)未鎖定茎刚、賬戶(hù)可用襟锐、賬戶(hù)未過(guò)期,如果上面的方法只要有一個(gè)返回false膛锭,就會(huì)拋出異常粮坞,那么認(rèn)證就會(huì)失敗。
additionalAuthenticationChecks是附加檢查初狰,是個(gè)抽象方法莫杈,等下看子類(lèi)的具體實(shí)現(xiàn)。
下面還有個(gè)postAuthenticationChecks.check(user)后檢查奢入,在最下面的DefaultPostAuthenticationChecks內(nèi)部類(lèi)中可以看到筝闹,它會(huì)檢查密碼未過(guò)期,如果為false就會(huì)拋出異常
如果上面的檢查都通過(guò)并且沒(méi)有異常俊马,表示認(rèn)證通過(guò)丁存,會(huì)調(diào)用下面的方法:
createSuccessAuthentication(principalToReturn, authentication, user);
跟進(jìn)發(fā)現(xiàn)此時(shí)通過(guò)構(gòu)造方法實(shí)例化對(duì)象UsernamePasswordAuthenticationToken時(shí)肩杈,調(diào)用的是三個(gè)參數(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}
此時(shí)會(huì)調(diào)用父類(lèi)的構(gòu)造方法設(shè)置權(quán)限信息柴我,并調(diào)用父類(lèi)的setAuthenticated(true)方法,到這里就表示認(rèn)證通過(guò)了扩然。
下面我們看看AbstractUserDetailsAuthenticationProvider的子類(lèi)艘儒,同ctrl+h可查看繼承關(guān)系,找到DaoAuthenticationProvider
4.DaoAuthenticationProvider類(lèi)
(1)查看additionalAuthenticationChecks附加檢查方法夫偶,它主要是檢查用戶(hù)密碼的正確性界睁,如果密碼為空或者錯(cuò)誤都會(huì)拋出異常
(2)獲取用戶(hù)信息UserDetails的retrieveUser方法,主要看下面這段代碼:
UserDetails loadedUser=this.getUserDetailsService().loadUserByUsername(username);
它是調(diào)用了getUserDetailsService先獲取到UserDetailsService對(duì)象兵拢,通過(guò)調(diào)用UserDetailsService對(duì)象的loadUserByUsername方法獲取用戶(hù)信息UserDetails
找到UserDetailsService翻斟,發(fā)現(xiàn)它是一個(gè)接口,查看繼承關(guān)系说铃,有很多實(shí)現(xiàn)访惜,都是spring-security提供的實(shí)現(xiàn)類(lèi)嘹履,并不滿(mǎn)足我們的需要,我們想自己制定獲取用戶(hù)信息的邏輯债热,所以我們可以實(shí)現(xiàn)這個(gè)接口砾嫉。比如從我們的數(shù)據(jù)庫(kù)中查找用戶(hù)信息
5.SecurityContextPersistenceFilter過(guò)濾器
那么用戶(hù)認(rèn)證成功之后,又是怎么保存認(rèn)證信息的呢窒篱,在下一次請(qǐng)求過(guò)來(lái)是如何判斷該用戶(hù)是否已經(jīng)認(rèn)證了呢焕刮?
請(qǐng)求進(jìn)來(lái)時(shí)會(huì)經(jīng)過(guò)SecurityContextPersistenceFilter過(guò)濾器,進(jìn)入SecurityContextPersistenceFilter過(guò)濾器并找到以下代碼:
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
從session中獲取SecurityContext對(duì)象墙杯,如果沒(méi)有就實(shí)例化一個(gè)SecurityContext對(duì)象
SecurityContextHolder.setContext(contextBeforeChainExecution);
將SecurityContext對(duì)象設(shè)置到SecurityContextHolder中
chain.doFilter(holder.getRequest(),holder.getResponse());
表示放行配并,執(zhí)行下一個(gè)過(guò)濾器
執(zhí)行完后面的過(guò)濾并經(jīng)過(guò)servlet處理之后,響應(yīng)給瀏覽器之前再次經(jīng)過(guò)此過(guò)濾器霍转。查看以下代碼:
SecurityContext contextAfterChainExecution=SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution,holder.getRequest(),holder.getResponse());
通過(guò)SecurityContextHolder獲取SecurityContext對(duì)象荐绝,然后清除SecurityContext,最后將獲取的SecurityContext對(duì)象放入session中
其中SecurityContextHolder是與ThreadLocal綁定的避消,即本線程內(nèi)所有的方法都可以獲得SecurityContext對(duì)象低滩,而SecurityContext對(duì)象中包含了Authentication對(duì)象,即用戶(hù)的認(rèn)證信息岩喷,spring-security判斷用戶(hù)是否認(rèn)證主要是根據(jù)SecurityContext中的Authentication對(duì)象來(lái)判斷恕沫。Authentication對(duì)象的詳細(xì)信息如圖:
最后整個(gè)過(guò)程的流程大致如下圖: