本篇文章為大家展示了SpringSecurity認(rèn)證流程是怎樣的,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。
專注于為中小企業(yè)提供網(wǎng)站設(shè)計(jì)制作、成都做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)鄰水免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了成百上千企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
前言
Spring Seuciry相關(guān)的內(nèi)容看了實(shí)在是太多了,但總覺得還是理解地不夠鞏固,還是需要靠知識(shí)輸出做鞏固。
相關(guān)版本:
java: jdk 8 spring-boot: 2.1.6.RELEASE
過(guò)濾器鏈和認(rèn)證過(guò)程
一個(gè)認(rèn)證過(guò)程,其實(shí)就是過(guò)濾器鏈上的一個(gè)綠色矩形Filter所要執(zhí)行的過(guò)程。
基本的認(rèn)證過(guò)程有三步驟:
Filter攔截請(qǐng)求,生成一個(gè)未認(rèn)證的
Authentication
,交由
AuthenticationManager
進(jìn)行認(rèn)證;
AuthenticationManager
的默認(rèn)實(shí)現(xiàn)
ProviderManager
會(huì)通過(guò)
AuthenticationProvider
對(duì)
Authentication
進(jìn)行認(rèn)證,其本身不做認(rèn)證處理; 如果認(rèn)證通過(guò),則創(chuàng)建一個(gè)認(rèn)證通過(guò)的
Authentication
返回;否則拋出異常,以表示認(rèn)證不通過(guò)。
要理解這個(gè)過(guò)程,可以從類UsernamePasswordAuthenticationFilter
,ProviderManager
,DaoAuthenticationProvider
和InMemoryUserDetailsManager
(UserDetailsService
實(shí)現(xiàn)類,由UserDetailsServiceAutoConfiguration
默認(rèn)配置提供)進(jìn)行了解。只要?jiǎng)?chuàng)建一個(gè)含有spring-boot-starter-security
的springboot項(xiàng)目,在適當(dāng)?shù)卮蛏蠑帱c(diǎn)接口看到這個(gè)流程。
用認(rèn)證部門進(jìn)行講解
)
請(qǐng)求到前臺(tái)
之后,負(fù)責(zé)該請(qǐng)求的前臺(tái)
會(huì)將請(qǐng)求的內(nèi)容封裝為一個(gè)Authentication
對(duì)象交給認(rèn)證管理部門
,認(rèn)證管理部門
僅管理認(rèn)證部門
,不做具體的認(rèn)證操作,具體的操作由與該前臺(tái)
相關(guān)的認(rèn)證部門
進(jìn)行處理。當(dāng)然,每個(gè)認(rèn)證部門
需要判斷Authentication
是否為該部門負(fù)責(zé),是則由該部門負(fù)責(zé)處理,否則交給下一個(gè)部門處理。認(rèn)證部門
認(rèn)證成功之后會(huì)創(chuàng)建一個(gè)認(rèn)證通過(guò)的Authentication
返回。否則要么拋出異常表示認(rèn)證不通過(guò),要么交給下一個(gè)部門處理。
如果需要新增認(rèn)證類型,只要增加相應(yīng)的前臺(tái)(Filter)
和與該前臺(tái)(Filter)
想對(duì)應(yīng)的認(rèn)證部門(AuthenticationProvider)
就即可,當(dāng)然也可以增加一個(gè)與已有前臺(tái)對(duì)應(yīng)的認(rèn)證部門
。認(rèn)證部門
會(huì)通過(guò)前臺(tái)
生成的Authentication
來(lái)判斷該認(rèn)證是否由該部門負(fù)責(zé),因而也許提供一個(gè)兩者相互認(rèn)同的Authentication
.
認(rèn)證部門
需要人員資料時(shí),則可以從人員資料部門
獲取。不同的系統(tǒng)有不同的人員資料部門
,需要我們提供該人員資料部門
,否則將拿到空白檔案。當(dāng)然,人員資料部門
不一定是唯一的,認(rèn)證部門
可以有自己的專屬資料部門
。
上圖還可以有如下的畫法:
這個(gè)畫法可能會(huì)和FilterChain更加符合。每一個(gè)前臺(tái)其實(shí)就是FilterChain中的一個(gè),客戶拿著請(qǐng)求逐個(gè)前臺(tái)請(qǐng)求認(rèn)證,找到正確的前臺(tái)之后進(jìn)行認(rèn)證判斷。
前臺(tái)(Filter)
這里的前臺(tái)Filter
僅僅指實(shí)現(xiàn)認(rèn)證的Filter,Spring Security Filter Chain中處理這些Filter還有其他的Filter,比如CsrfFilter
。如果非要給角色給他們,那么就當(dāng)他們是保安人員
吧。
Spring Security為我們提供了3個(gè)已經(jīng)實(shí)現(xiàn)的Filter。UsernamePasswordAuthenticationFilter
,BasicAuthenticationFilter
和 RememberMeAuthenticationFilter
。如果不做任何個(gè)性化的配置,UsernamePasswordAuthenticationFilter
和BasicAuthenticationFilter
會(huì)在默認(rèn)的過(guò)濾器鏈中。這兩種認(rèn)證方式也就是默認(rèn)的認(rèn)證方式。
UsernamePasswordAuthenticationFilter
僅僅會(huì)對(duì)/login
路徑生效,也就是說(shuō)UsernamePasswordAuthenticationFilter
負(fù)責(zé)發(fā)布認(rèn)證,發(fā)布認(rèn)證的接口為/login
。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { ... public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } ...}
UsernamePasswordAuthenticationFilter
為抽象類AbstractAuthenticationProcessingFilter
的一個(gè)實(shí)現(xiàn),而BasicAuthenticationFilter
為抽象類BasicAuthenticationFilter
的一個(gè)實(shí)現(xiàn)。這四個(gè)類的源碼提供了不錯(cuò)的前臺(tái)(Filter)
實(shí)現(xiàn)思路。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
提供了認(rèn)證前后需要做的事情,其子類只需要提供實(shí)現(xiàn)完成認(rèn)證的抽象方法attemptAuthentication(HttpServletRequest, HttpServletResponse)
即可。使用AbstractAuthenticationProcessingFilter
時(shí),需要提供一個(gè)攔截路徑(使用AntPathMatcher
進(jìn)行匹配)來(lái)攔截對(duì)應(yīng)的特定的路徑。
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter
作為實(shí)際的前臺(tái),會(huì)將客戶端提交的username和password封裝成一個(gè)UsernamePasswordAuthenticationToken
交給認(rèn)證管理部門(AuthenticationManager)
進(jìn)行認(rèn)證。如此,她的任務(wù)就完成了。
BasicAuthenticationFilter 該前臺(tái)(Filter)
只會(huì)處理含有Authorization
的Header,且小寫化后的值以basic
開頭的請(qǐng)求,否則該前臺(tái)(Filter)
不負(fù)責(zé)處理。該Filter會(huì)從header中獲取Base64編碼之后的username和password,創(chuàng)建UsernamePasswordAuthenticationToken
提供給認(rèn)證管理部門(AuthenticationMananager)
進(jìn)行認(rèn)證。
認(rèn)證資料(Authentication)
前臺(tái)
接到請(qǐng)求之后,會(huì)從請(qǐng)求中獲取所需的信息,創(chuàng)建自家認(rèn)證部門(AuthenticationProvider)
所認(rèn)識(shí)的認(rèn)證資料(Authentication)
,認(rèn)證部門(AuthenticationProvider)
則主要是通過(guò)認(rèn)證資料(Authentication)
的類型判斷是否由該部門處理。
public interface Authentication extends Principal, Serializable { // 該principal具有的權(quán)限。AuthorityUtils工具類提供了一些方便的方法。 Collection extends GrantedAuthority> getAuthorities(); // 證明Principal的身份的證書,比如密碼。 Object getCredentials(); // authentication request的附加信息,比如ip。 Object getDetails(); // 當(dāng)事人。在username+password模式中為username,在有userDetails之后可以為userDetails。 Object getPrincipal(); // 是否已經(jīng)通過(guò)認(rèn)證。 boolean isAuthenticated(); // 設(shè)置通過(guò)認(rèn)證。 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;}
在Authentication
被認(rèn)證之后,會(huì)保存到一個(gè)thread-local的SecurityContext中。
// 設(shè)置SecurityContextHolder.getContext().setAuthentication(anAuthentication);// 獲取Authentication existingAuth = SecurityContextHolder.getContext() .getAuthentication();
在寫前臺(tái)Filter
的時(shí)候,可以先檢查SecurityContextHolder.getContext()
中是否已經(jīng)存在通過(guò)認(rèn)證的Authentication
了,如果存在,則可以直接跳過(guò)該Filter。已經(jīng)通過(guò)驗(yàn)證的Authentication
建議設(shè)置為一個(gè)不可修改的實(shí)例。
目前從Authentication
的類圖中看到的實(shí)現(xiàn)類,均為Authentication
的抽象子類AbstractAuthenticationToken
的實(shí)現(xiàn)類。實(shí)現(xiàn)類有好幾個(gè),與前面的講到的Filter相關(guān)的有UsernamePasswordAuthenticationToken
和RememberMeAuthenticationToken
。
AbstractAuthenticationToken
為CredentialsContainer
和Authentication
的子類。實(shí)現(xiàn)了一些簡(jiǎn)單的方法,但主要的方法還需要實(shí)現(xiàn)。該類的getName()
方法的實(shí)現(xiàn)可以看到常用的principal類為UserDetails
、AuthenticationPrincipal
和Princial
。如果有需要將對(duì)象設(shè)置為principal,可以考慮繼承這三個(gè)類中的一個(gè)。
public String getName() { if (this.getPrincipal() instanceof UserDetails) { return ((UserDetails) this.getPrincipal()).getUsername(); } if (this.getPrincipal() instanceof AuthenticatedPrincipal) { return ((AuthenticatedPrincipal) this.getPrincipal()).getName(); } if (this.getPrincipal() instanceof Principal) { return ((Principal) this.getPrincipal()).getName(); } return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();}
認(rèn)證管理部門(AuthenticationManager)
AuthenticationManager
是一個(gè)接口,認(rèn)證Authentication
,如果認(rèn)證通過(guò)之后,返回的Authentication
應(yīng)該帶上該principal所具有的GrantedAuthority
。
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException;}
該接口的注釋中說(shuō)明,必須按照如下的異常順序進(jìn)行檢查和拋出:
DisabledException:賬號(hào)不可用 LockedException:賬號(hào)被鎖 BadCredentialsException:證書不正確
Spring Security提供一個(gè)默認(rèn)的實(shí)現(xiàn)ProviderManager
。認(rèn)證管理部門(ProviderManager)
僅執(zhí)行管理職能,具體的認(rèn)證職能由認(rèn)證部門(AuthenticationProvider)
執(zhí)行。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ... public ProviderManager(List
遍歷所有的認(rèn)證部門(AuthenticationProvider),找到支持的認(rèn)證部門進(jìn)行認(rèn)證認(rèn)證部門進(jìn)行認(rèn)證認(rèn)證通過(guò)則不再進(jìn)行下一個(gè)認(rèn)證部門的認(rèn)證,否則拋出的異常被捕獲,執(zhí)行下一個(gè)認(rèn)證部門(AuthenticationProvider)如果認(rèn)證通過(guò),執(zhí)行認(rèn)證通過(guò)之后的操作如果認(rèn)證不通過(guò),必然有拋出異常,否則表示沒有配置相應(yīng)的認(rèn)證部門(AuthenticationProvider)
當(dāng)使用到Spring Security OAuth3的時(shí)候,會(huì)看到另一個(gè)實(shí)現(xiàn)OAuth3AuthenticationManager
。
認(rèn)證部門(AuthenticationProvider)
認(rèn)證部門(AuthenticationProvider)
負(fù)責(zé)實(shí)際的認(rèn)證工作,與認(rèn)證管理部門(ProvderManager)
協(xié)同工作。也許其他的認(rèn)證管理部門(AuthenticationManager)
并不需要認(rèn)證部門(AuthenticationProvider)
的協(xié)作。
public interface AuthenticationProvider { // 進(jìn)行認(rèn)證 Authentication authenticate(Authentication authentication) throws AuthenticationException; // 是否由該AuthenticationProvider進(jìn)行認(rèn)證 boolean supports(Class> authentication);}
該接口有很多的實(shí)現(xiàn)類,其中包含了RememberMeAuthenticationProvider
(直接AuthenticationProvider)和DaoAuthenticationProvider
(通過(guò)AbastractUserDetailsAuthenticationProvider
簡(jiǎn)介繼承)。這里重點(diǎn)講講AbastractUserDetailsAuthenticationProvider
和DaoAuthenticationProvider
。
AbastractUserDetailsAuthenticationProvider
顧名思義,AbastractUserDetailsAuthenticationProvider
是對(duì)UserDetails
支持的Provider,其他的Provider,如RememberMeAuthenticationProvider就不需要用到UserDetails
。該抽象類有兩個(gè)抽象方法需要實(shí)現(xiàn)類完成:
// 獲取 UserDetailsprotected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
retrieveUser()
方法為校驗(yàn)提供UserDetails
。先看下UserDetails:
public interface UserDetails extends Serializable { Collection extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); // 賬號(hào)是否過(guò)期 boolean isAccountNonExpired(); // 賬號(hào)是否被鎖 boolean isAccountNonLocked(); // 證書(password)是否過(guò)期 boolean isCredentialsNonExpired(); // 賬號(hào)是否可用 boolean isEnabled();}
AbastractUserDetailsAuthenticationProvider#authentication(Authentication)
分為三步驗(yàn)證:
preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); postAuthenticationChecks.check(user);
preAuthenticationChecks
的默認(rèn)實(shí)現(xiàn)為DefaultPreAuthenticationChecks
,負(fù)責(zé)完成校驗(yàn):
UserDetails#isAccountNonLocked() UserDetails#isEnabled() UserDetails#isAccountNonExpired()
postAuthenticationChecks
的默認(rèn)實(shí)現(xiàn)為DefaultPostAuthenticationChecks
,負(fù)責(zé)完成校驗(yàn):
UserDetails#user.isCredentialsNonExpired()
additionalAuthenticationChecks
需要由實(shí)現(xiàn)類完成。
校驗(yàn)成功之后,AbstractUserDetailsAuthenticationProvider
會(huì)創(chuàng)建并返回一個(gè)通過(guò)認(rèn)證的Authentication
。
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { // Ensure we return the original credentials the user supplied, // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result;}
DaoAuthenticationProvider
如下為DaoAuthenticationProvider
對(duì)AbstractUserDetailsAuthenticationProvider
抽象方法的實(shí)現(xiàn)。
// 檢查密碼是否正確protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } 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")); }}// 通過(guò)資料室(UserDetailsService)獲取UserDetails對(duì)象protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } ...}
在以上的代碼中,需要提供UserDetailsService
和PasswordEncoder
實(shí)例。只要實(shí)例化這兩個(gè)類,并放入到Spring容器中即可。
資料部門(UserDetailsService)
UserDetailsService
接口提供認(rèn)證過(guò)程所需的UserDetails
的類,如DaoAuthenticationProvider
需要一個(gè)UserDetailsService
實(shí)例。
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}
Spring Security提供了兩個(gè)UserDetailsService
的實(shí)現(xiàn):InMemoryUserDetailsManager
和JdbcUserDetailsManager
。InMemoryUserDetailsManager
為默認(rèn)配置,從UserDetailsServiceAutoConfiguration
的配置中可以看出。當(dāng)然也不容易理解,基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)需要增加數(shù)據(jù)庫(kù)的配置,不適合做默認(rèn)實(shí)現(xiàn)。這兩個(gè)類均為UserDetailsManager
的實(shí)現(xiàn)類,UserDetailsManager
定義了UserDetails
的CRUD操作。InMemoryUserDetailsManager
使用Map
做存儲(chǔ)。
public interface UserDetailsManager extends UserDetailsService { void createUser(UserDetails user); void updateUser(UserDetails user); void deleteUser(String username); void changePassword(String oldPassword, String newPassword); boolean userExists(String username);}
如果我們需要增加一個(gè)UserDetailsService
,可以考慮實(shí)現(xiàn)UserDetailsService
或者UserDetailsManager
。
增加一個(gè)認(rèn)證流程
到這里,我們已經(jīng)知道Spring Security的流程了。從上面的內(nèi)容可以知道,如要增加一個(gè)新的認(rèn)證方式,只要增加一個(gè)[前臺(tái)(Filter)
+ 認(rèn)證部門(AuthenticationProvider)
+ 資料室(UserDetailsService)
]組合即可。事實(shí)上,資料室(UserDetailsService)
不是必須的,可根據(jù)認(rèn)證部門(AuthenticationProvider)
需要實(shí)現(xiàn)。
我會(huì)在另一篇文章中以手機(jī)號(hào)碼+驗(yàn)證碼登錄為例進(jìn)行講解。
上述內(nèi)容就是SpringSecurity認(rèn)證流程是怎樣的,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。