Spring?Security認證器實現(xiàn)過程詳解
一些權(quán)限框架一般都包含認證器和決策器,前者處理登陸驗證,后者處理訪問資源的控制
Spring Security的登陸請求處理如圖
下面來分析一下是怎么實現(xiàn)認證器的
攔截請求
首先登陸請求會被UsernamePasswordAuthenticationFilter
攔截,這個過濾器看名字就知道是一個攔截用戶名密碼的攔截器
主要的驗證是在attemptAuthentication()
方法里,他會去獲取在請求中的用戶名密碼,并且創(chuàng)建一個該用戶的上下文,然后在去執(zhí)行一個驗證過程
String username = this.obtainUsername(request); String password = this.obtainPassword(request); //創(chuàng)建上下文 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
可以看看UsernamePasswordAuthenticationToken
這個類,他是繼承了AbstractAuthenticationToken
,然后這個父類實現(xiàn)了Authentication
由這個類的方法和屬性可得知他就是存儲用戶驗證信息的,認證器的主要功能應(yīng)該就是驗證完成后填充這個類
回到UsernamePasswordAuthenticationToken
中,在上面創(chuàng)建的過程了可以發(fā)現(xiàn)
public UsernamePasswordAuthenticationToken(Object principal,Object credentials){ super(null); this.principal=principal; this.credentials=credentials; //還沒認證 setAuthenticated(false); }
還有一個super(null)
的處理,因為剛進來是還不知道有什么權(quán)限的,設(shè)置null是初始化一個空的權(quán)限
//權(quán)限利集合 private final Collection<GrantedAuthority> authorities; //空的集合 public static final List<GrantedAuthority> NO_AUTHORITIES = Collections.emptyList(); //初始化 if (authorities == null) { this.authorities = AuthorityUtils.NO_AUTHORITIES; return; }
那么后續(xù)認證完還會把權(quán)限設(shè)置盡量,此時可以看UsernamePasswordAuthenticationToken
的另一個重載構(gòu)造器
//認證完成 public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // must use super, as we override }
在看源碼的過程中,注釋一直在強調(diào)這些上下文的填充和設(shè)置都應(yīng)該是由AuthenticationManager
或者AuthenticationProvider
的實現(xiàn)類去操作
驗證過程
接下來會把球踢給AuthenticationManager
,但他只是個接口
/** * Attempts to authenticate the passed {@link Authentication} object, returning a * fully populated <code>Authentication</code> object (including granted authorities) * if successful. **/ public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
注釋也寫的很清楚了,認證完成后會填充Authentication
接下來會委托給ProviderManager
,因為他實現(xiàn)了AuthenticationManager
剛進來看authenticate()
方法會發(fā)現(xiàn)他先遍歷了一個List<AuthenticationProvider>
集合
/** * Indicates a class can process a specific Authentication **/ public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; //支不支持特定類型的authentication boolean supports(Class<?> authentication); }
實現(xiàn)這個類就可以處理不同類型的Authentication
,比如上邊的UsernamePasswordAuthenticationToken
,對應(yīng)的處理類是AbstractUserDetailsAuthenticationProvider
,為啥知道呢,因為在這個supports()
里
public boolean supports(Class<?> authentication) { return (UsernamePasswordAuthenticationToken.class .isAssignableFrom(authentication)); }
注意到這個是抽象類,實際的處理方法是在他的子類DaoAuthenticationProvider
里,但是最重要的authenticate()
方法子類好像沒有繼承,看看父類是怎么實現(xiàn)這個方法的
首先是繼續(xù)判斷Authentication
是不是特定的類
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
查詢根據(jù)用戶名用戶,這次就是到了子類的方法了,因為這個方法是抽象的
user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
接著DaoAuthenticationProvider
會調(diào)用真正實現(xiàn)查詢用戶的類UserDetailsService
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
UserDetailsService
這個類信息就不陌生了,我們一般都會去實現(xiàn)這個類來自定義查詢用戶的方式,查詢完后會返回一個UserDetails
,當(dāng)然也可以繼承這個類來擴展想要的字段,主要填充的是權(quán)限信息和密碼
檢驗用戶,如果獲取到的UserDetails
是null,則拋異常,不為空則繼續(xù)校驗
//檢驗用戶合法性 preAuthenticationChecks.check(user); //校驗密碼 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
第一個教育是判斷用戶的合法性,就是判斷UserDetails
里的幾個字段
//賬號是否過期 boolean isAccountNonExpired(); //賬號被鎖定或解鎖狀態(tài)。 boolean isAccountNonLocked(); //密碼是否過期 boolean isCredentialsNonExpired(); //是否啟用 boolean isEnabled();
第二個則是由子類實現(xiàn)的,判斷從數(shù)據(jù)庫獲取的密碼和請求中的密碼是否一致,因為用的登陸方式是根據(jù)用戶名稱登陸,所以有檢驗密碼的步驟
String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); }
需要主要的是請求中的密碼是被加密過的,所以從數(shù)據(jù)庫獲取到的密碼也應(yīng)該是被加密的
注意到當(dāng)完成校驗的時候會把信息放入緩存
//當(dāng)沒有從緩存中獲取到值時,這個字段會被設(shè)置成false if (!cacheWasUsed) { this.userCache.putUserInCache(user); } //下次進來的時候回去獲取 UserDetails user = this.userCache.getUserFromCache(username);
如果是從緩存中獲取,也是會走檢驗邏輯的
最后完成檢驗,并填充一個完整的Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
由上述流程來看,Security的檢驗過程還是比較清晰的,通過AuthenticationManager
來委托給ProviderManager
,在通過具體的實現(xiàn)類來處理請求,在這個過程中,將查詢用戶的實現(xiàn)和驗證代碼分離開來
整個過程看著像是策略模式,后邊將變化的部分抽離出來,實現(xiàn)解耦
返回完整的Authentication
前邊提到的認證成功會調(diào)用createSuccessAuthentication()
方法,里邊的內(nèi)容很簡單
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails());
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // must use super, as we override }
這次往supe里放了權(quán)限集合,父類的處理是判斷里邊的權(quán)限有沒有空的,沒有則轉(zhuǎn)換為只讀集合
for (GrantedAuthority a : authorities) { if (a == null) { throw new IllegalArgumentException( "Authorities collection cannot contain any null elements"); } } ArrayList<GrantedAuthority> temp = new ArrayList<>( authorities.size()); temp.addAll(authorities); this.authorities = Collections.unmodifiableList(temp);
收尾工作
回到ProviderManager里的authenticate方法,當(dāng)我們終于從
result = provider.authenticate(authentication);
走出來時,后邊還有什么操作
1.將返回的用戶信息負責(zé)給當(dāng)前的上下文
if (result != null) { copyDetails(authentication, result); break; }
2.刪除敏感信息
((CredentialsContainer) result).eraseCredentials();
這個過程會將一些字段設(shè)置為null,可以實現(xiàn)eraseCredentials()
方法來自定義需要刪除的信息
最后返回到UsernamePasswordAuthenticationFilter
中通過過濾
結(jié)論
這就是Spring Security實現(xiàn)認證的過程了
通過實現(xiàn)自己的上下文Authentication
和處理類AuthenticationProvider
以及具體的查詢用戶的方法就可以自定義自己的登陸實現(xiàn)
具體可以看Spring Security自定義認證器
到此這篇關(guān)于Spring Security認證器實現(xiàn)過程詳解的文章就介紹到這了,更多相關(guān)Spring Security認證器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用java反射機制實現(xiàn)自動調(diào)用類的簡單方法
下面小編就為大家?guī)硪黄胘ava反射機制實現(xiàn)自動調(diào)用類的簡單方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08Java數(shù)據(jù)類型之引用數(shù)據(jù)類型解讀
這篇文章主要介紹了Java數(shù)據(jù)類型之引用數(shù)據(jù)類型,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07SpringBoot大學(xué)心理服務(wù)系統(tǒng)實現(xiàn)流程分步講解
本系統(tǒng)主要論述了如何使用JAVA語言開發(fā)一個大學(xué)生心理服務(wù)系統(tǒng) ,本系統(tǒng)將嚴格按照軟件開發(fā)流程進行各個階段的工作,采用B/S架構(gòu),面向?qū)ο缶幊趟枷脒M行項目開發(fā)2022-09-09Spring boot如何通過@Scheduled實現(xiàn)定時任務(wù)及多線程配置
這篇文章主要介紹了Spring boot如何通過@Scheduled實現(xiàn)定時任務(wù)及多線程配置,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12idea 在springboot中使用lombok插件的方法
這篇文章主要介紹了idea 在springboot中使用lombok的相關(guān)資料,通過代碼給大家介紹在pom.xml中引入依賴的方法,本文給大家介紹的非常詳細,需要的朋友可以參考下2021-08-08在CentOS7(有圖形化界面)上安裝maven和idea的詳細教程
這篇文章主要介紹了在CentOS7(有圖形化界面)上安裝maven和idea的詳細教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03