上一期咱們分享了Spring Security是如何經過AbstractAuthenticationProcessingFilter
向Web應用向基於HTTP、瀏覽器的請求提供身份驗證服務的。 這一次咱們針對最經常使用,也是Spring Security默認在HTTP上使用的驗證過濾器UsernamePasswordAuthenticationFilter
即基於用戶名和密碼的身份驗證過濾器是如何與核心進行交互進行展開說明。目的是但願讓你們對如何在Spring Security的核心上完成一個指定的身份驗證協議的擴展工做,已經涉及相關主要組件及其角色職責有個初步的瞭解。 這一期的內容若是有了前幾期對身份驗證核心的背景,相對來講比較的簡單,由於整個流程就是在原有的基礎上更加具體化了場景:身份驗證的數據來源是用戶提交的請求,驗證的憑證是用戶名和密碼。因爲這樣的緣由,這一期更像是對前幾期的一個綜合性的應用總結。java
UsernamePasswordAuthenticationFilter
的職責和實現UsernamePasswordAuthenticationToken
封裝的身份驗證信息UsernamePasswordAuthenticationFilter
是AbstractAuthenticationProcessingFilter
針對使用用戶名和密碼進行身份驗證而定製化的一個過濾器。 在一開始咱們先經過下面的配圖來回憶一下咱們的老朋友AbstractAuthenticationProcessingFilter
的在框架中的角色與職責。 瀏覽器
AbstractAuthenticationProcessingFilter
在整個身份驗證的流程中主要處理的工做就是全部與Web資源相關的事情,而且將其封裝成Authentication對象,最後調用AuthenticationManager
的驗證方法。以UsernamePasswordAuthenticationFilter
的工做大體也是如此,只不過在這個場景下更加明確了Authentication
對象的封裝數據的來源和形式:使用用戶名和密碼。bash
接着咱們再UsernamePasswordAuthenticationFilter
的屬性和方法作一個快速的瞭解。 UsernamePasswordAuthenticationFilter
繼承擴展了AbstractAuthenticationProcessingFilter
,相對與AbstractAuthenticationProcessingFilter
而言主要有如下幾個改動:框架
- 屬性中增長了username和password字段;
- 強制的只對POST請求應用;
- 重寫了attemptAuthentication身份驗證入口方法。
關於增長username和password的動機十分容易明白,在從請求中獲取表單提交的用戶名和密碼字段便會賦值到這兩個屬性中。 而重寫attemptAuthentication
的方法主要是完了構建一個UsernamePasswordAuthenticationToken
對象而且將其傳遞給AuthenticationManager
進行身份驗證。ide
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
複製代碼
UsernamePasswordAuthenticationToken
是整個身份驗證流程封裝了身份驗證請求數據中的數據對象。 在UsernamePasswordAuthenticationFilter
的屬性聲明中額外增長了username和password的動機很容易明白,即須要從HttpRequest
中獲取對應的參數字段,並將其封裝進Authentication
中傳遞給AuthenticationManager
進行身份驗證這裏讓咱們回顧下Authentication
究竟是什麼?Authentication
是一個接口聲明,一個特定行爲的聲明,它並非一個類,沒有辦法實例化爲對象進行傳遞。因此咱們首先須要對Authentication
進行實現,使其能夠被實例化。 this
在UsernamePasswordAuthenticationFilter
的身份驗證設計裏,咱們須要驗證協議用簡單的語言能夠描述爲:給我一組用戶名和密碼,若是匹配,那麼就算驗證成功。用戶名便是一個惟一能夠標識不一樣用戶的字段,而密碼則是檢驗當前的身份驗證是否正確的憑證信息。在Spring Security中便將使用username和password封裝成Authentication的實現聲明爲了UsernamePasswordAuthenticationToken
。spa
UsernamePasswordAuthenticationToken
繼承了AbstractAuthenticationToken
抽象類,其主要與AbstractAuthenticationToken
的區分就是針對使用用戶名和密碼驗證的請求按照約定進行了必定的封裝:將username賦值到了principal ,而將password賦值到了credentials。設計
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
}
複製代碼
經過UsernamePasswordAuthenticationToken
實例化了Authentication
接口,繼而按照流程,將其傳遞給AuthenticationManager
調用身份驗證核心完成相關工做。3d
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
複製代碼
以上未來自HTTP請求中的參數按照預先約定放入賦值給Authentication
指定屬性,即是UsernamePasswordAuthenticationFilter
部分最主要的改動。code
Web層的工做已經完成了,Authentication
接口的實現類UsernamePasswordAuthenticationToken
經過AuthenticationManager
提供的驗證方法做爲參數被傳遞到了身份驗證的核心組件中。 咱們曾屢次強調過一個設計概念:AuthenticationManager
接口設計上並非用於完成特定的身份驗證工做的,而是調用其所配發的AuthenticationProvider
接口去實現的。 那麼這裏就有一個疑問,針對接口聲明參數聲明的Authentication
,針對不一樣驗證協議的AuthenticationProvider
的實現類們是完成對應的工做的,而且AuthenticationManager
是如何知道應該使用哪個AuthenticationProvider
才能完成對應協議的驗證工做? 那麼咱們首先先複習下驗證核心的大明星AuthenticationProvider
接口的聲明:
AuthenticationProvider
只包含兩個方法聲明:
一個是用於驗證身份請求AuthenticationToken的authenticate方法:
Authentication authenticate(Authentication authentication) throws AuthenticationException;
複製代碼
另一個即是讓AuthenticationManager
能夠經過調用該方法辨別當前AuthenticationProvider
是不是完成相應驗證工做的supports方法:
boolean supports(Class<?> authentication);
複製代碼
對於AuthenticationProvider
整個體系能說的很是多,本期只對咱們「須要瞭解」的AuthenticationProvider
中兩個接口聲明的方法作個最簡單的說明。其餘部分在之後單獨對AuthenticationProvider
體系介紹的時候再進一步展開。
在Spring Security中惟一AuthenticationManager
的實現類ProviderManager
,在處理authenticate身份驗證入口方法的時,首先第一解決的問題即是:我手下哪一個AuthenticationProvider
能驗證當前傳入的Authentication
?爲此ProviderManage
r便會對其全部的AuthenticationProvider
作supports方法檢測,直到有AuthenticationProvider
能在supports方法被調用後返回true。
咱們瞭解了框架上的設計邏輯:先要知道知道誰能處理當前的身份驗證信息請求再要求它進行驗證工做。 回到咱們的場景上來:UsernamePasswordAuthenticationFilter
已經封裝好了一個UsernamePasswordAuthenticationToken
,並將它傳遞給了ProviderMananger
。隨後ProviderMananger
便會一次輪訓它管理的全部AuthenticationProvider
,詢問是否有誰能支持這個Authentication
的實現類。此時ProviderMananger所處的狀況大概就跟下圖通常困惑:
在ProviderMananger的視角里,全部的Authentication實現類都不具名,它不只不能經過自身完成驗證工做也不能獨立完成判斷是否支持的工做,而是通通交給AuthenticationProvider去完成。而不一樣的AuthenticationProvider開發初衷本就是爲了支持指定的某種驗證協議,因此在特定的AuthenticationProvider的視角中,他只關心當前Authentication是否是他預先設計處理的類型便可。 在使用用戶名和密碼的驗證場景中,驗證使用的用戶名和密碼被封裝成了UsernamePasswordAuthenticationToken對象。Spring Security便爲了向UsernamePasswordAuthenticationToken對象在覈心層提供相關的驗證服務便繼承AuthenticationProvider開發了使用用戶名和密碼與UserDetailsService交互而且驗證密碼的DaoAuthenticationProvider
。 DaoAuthenticationProvide
r是AbstractUserDetailsAuthenticationProvider
的實現類,DaoAuthenticationProvider
針對UsernamePasswordAuthenticationToken
的大部分邏輯都是經過AbstractUserDetailsAuthenticationProvider
完成的。好比針對ProviderManager
詢問是否支持當前Authentication
的supports方法:
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
複製代碼
可能有些同窗對isAssignableFrom方法比較陌生,這是一個判斷兩個類之間是否存在繼承關係使用的判斷方法,DaoAuthenticationProvider會判斷當前的Authentication的實現類是不是UsernamePasswordAuthenticationToken它自己,或者是擴展了UsernamePasswordAuthenticationToken的子孫類。返回true的場景只有一種,即是當前的Authentication是UsernamePasswordAuthenticationToken實現,換言之即是DaoAuthenticationProvider設計上須要進行處理的某種特定的驗證協議的信息載體的實現。
完成了是否支持的supports驗證後,ProviderMananger便會全權將驗證工做交由DaoAuthenticationProvider進行處理了。與ProviderMananger最不一樣一點是,在DaoAuthenticationProvider的視角里,當前的Authentication最起碼必定是UsernamePasswordAuthenticationToken的形式了,不用和ProviderMananger同樣由於匱乏信息而不知道幹什麼。 在DaoAuthenticationProvider分別會按照預先設計同樣分別從principal和credentials獲取用戶名和密碼進行驗證。
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
String presentedPassword = authentication.getCredentials().toString();
複製代碼
接着即是按照咱們熟悉的預先設計流程,經過UserDetailsService使用username獲取對應的UserDetails,最後經過對比密碼是否一致,向PrivoderManager返回最終的身份驗證結果與身份信息。這樣一個特定場景使用用戶名和密碼的驗證流程就完成了。
咱們先來總結下,當前出現過的針對用戶名和密碼擴展過的類與其爲什麼被擴展的緣由。
本章的重點是介紹特定場景下框架是如何經過擴展指定組件來完成預設驗證邏輯的交互過程。其實整個驗證工做核心部分是在DaoAuthenticationProvider中進行完成的,可是這部份內容涉及到具體的驗證協議的實現邏輯很是複雜,本期就暫時略過,在一下期中咱們將對驗證核心最重要的組件AuthenticationProvider其依賴的組件和對應職責作一個全面的講解。 咱們下期再見。