前言
上一期我們分享了Spring Security是如何通過AbstractAuthenticationProcessingFilter向Web應用向基于HTTP攒岛、瀏覽器的請求提供身份驗證服務的。
這一次我們針對最常用裙士,也是Spring Security默認在HTTP上使用的驗證過濾器即基于用戶名和密碼的身份驗證過濾器是如何與核心進行交互進行展開說明。目的是希望讓大家對如何在Spring Security的核心上完成一個指定的身份驗證協(xié)議的擴展工作砂吞,已經(jīng)涉及相關主要組件及其角色職責有個初步的了解以现。
這一期的內(nèi)容如果有了前幾期對身份驗證核心的背景,相對來說比較的簡單坑资,因為整個流程就是在原有的基礎上更加具體化了場景:身份驗證的數(shù)據(jù)來源是用戶提交的請求,驗證的憑證是用戶名和密碼穆端。由于這樣的原因袱贮,這一期更像是對前幾期的一個綜合性的應用總結。
第四期 UsernamePasswordAuthenticationFilter詳細說明
本期的任務清單
- 了解UsernamePasswordAuthenticationFilter的職責和實現(xiàn)
1. 了解UsernamePasswordAuthenticationFilter的職責和實現(xiàn)
UsernamePasswordAuthenticationFilter類的說明
UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter針對使用用戶名和密碼進行身份驗證而定制化的一個過濾器体啰。
在一開始我們先通過下面的配圖來回憶一下我們的老朋友AbstractAuthenticationProcessingFilter的在框架中的角色與職責攒巍。
AbstractAuthenticationProcessingFilter在整個身份驗證的流程中主要處理的工作就是所有與Web資源相關的事情,并且將其封裝成Authentication對象荒勇,最后調(diào)用AuthenticationManager的驗證方法柒莉。所以UsernamePasswordAuthenticationFilter的工作大致也是如此,只不過在這個場景下更加明確了Authentication對象的封裝數(shù)據(jù)的來源和形式——使用用戶名和密碼沽翔。
接著我們再對的屬性和方法做一個快速的了解兢孝。UsernamePasswordAuthenticationFilter繼承擴展了AbstractAuthenticationProcessingFilter,相對與AbstractAuthenticationProcessingFilter而言主要有以下幾個改動:
- 屬性中增加了username和password字段仅偎;
- 強制的只對POST請求應用跨蟹;
- 重寫了attemptAuthentication身份驗證入口方法。
封裝用戶名密碼的基石:UsernamePasswordAuthenticationToken
在UsernamePasswordAuthenticationFilter的屬性聲明中額外增加了username和password的動機很容易明白橘沥,即需要從HttpRequest中獲取對應的參數(shù)字段窗轩,并將其封裝進Authentication中傳遞給AuthenticationManager進行身份驗證。這里讓我們回顧下Authentication到底是什么座咆?Authentication是一個接口聲明痢艺,一個特定行為的聲明,它并不是一個類介陶,沒有辦法實例化為對象進行傳遞腹备。所以我們首先需要對Authentication進行實現(xiàn),使其可以被實例化斤蔓。
在UsernamePasswordAuthenticationFilter的身份驗證設計里,我們需要驗證協(xié)議用簡單的語言可以描述為:給我一組用戶名和密碼镀岛,如果匹配弦牡,那么就算驗證成功友驮。用戶名即是一個唯一可以標識不同用戶的字段,而密碼則是檢驗當前的身份驗證是否正確的憑證信息驾锰。在Spring Security中便將使用username和password封裝成Authentication的實現(xiàn)聲明為了
UsernamePasswordAuthenticationToken繼承了抽象類,其主要與AbstractAuthenticationToken的區(qū)分就是針對使用用戶名和密碼驗證的請求按照約定進行了一定的封裝:將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接口,繼而按照流程赏酥,將其傳遞給AuthenticationMananger調(diào)用身份驗證核心完成相關工作喳整。
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
以上將來自HTTP請求中的參數(shù)按照預先約定放入賦值給Authentication指定屬性,便是UsernamePasswordAuthenticationFilter部分最主要的改動裸扶。
驗證核心的工作者:AuthenticationProvider
Web層的工作已經(jīng)完成了框都,Authentication接口的實現(xiàn)類UsernamePasswordAuthenticationToken通過AuthenticationMananger提供的驗證方法作為參數(shù)被傳遞到了身份驗證的核心組件中。
我們曾多次強調(diào)過一個設計概念:AuthenticationManager接口設計上并不是用于完成特定的身份驗證工作的呵晨,而是調(diào)用其所配發(fā)的AuthenticationProvider接口去實現(xiàn)的魏保。
那么這里就有一個疑問,針對接口聲明參數(shù)聲明的Authentication摸屠,針對不同驗證協(xié)議的AuthenticationProvider的實現(xiàn)類們是完成對應的工作的谓罗,并且AuthenticationManager是如何知道應該使用哪一個AuthenticationProvider才能完成對應協(xié)議的驗證工作?
那么我們首先先復習下驗證核心的大明星AuthenticationProvider接口的聲明:
AuthenticationProvider只包含兩個方法聲明季二,核心驗證方法入口:
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
另外一個便是讓AuthenticationManager可以通過調(diào)用該方法辨別當前AuthenticationProvider是否是完成相應驗證工作的supports方法:
boolean supports(Class<?> authentication);
簡單的描述便是AuthenticationProvider只有兩個方法檩咱,一個是它不能驗證當前的Authentication,還有便是讓他去驗證當前的Authentication戒傻。
對于AuthenticationProvider整個體系能說的非常多税手,本期只對我們“需要了解”的AuthenticationProvider中兩個接口聲明的方法做個最簡單的說明。其他部分在以后單獨對AuthenticationProvider體系介紹的時候再進一步展開需纳。
是否支持當前驗證協(xié)議:boolean supports(Class<?> authentication
在Spring Security中唯一AuthenticationManager的實現(xiàn)類芦倒,在處理authenticate身份驗證入口方法的時,首先第一解決的問題便是:我手下哪個AuthenticationProvider能驗證當前傳入的Authentication不翩?為此ProviderManager便會對其所有的AuthenticationProvider做supports方法檢測兵扬,直到有AuthenticationProvider能在supports方法被調(diào)用后返回true。
我們了解了框架上的設計邏輯:先要知道知道誰能處理當前的身份驗證信息請求再要求它進行驗證工作口蝠。
回到我們的場景上來:UsernamePasswordAuthenticationFilter已經(jīng)封裝好了一個UsernamePasswordAuthenticationToken傳遞給了ProviderMananger器钟。緊接著當前ProviderMananger正焦頭爛額的詢問哪個AuthenticationProvider能支持這個Authentication的實現(xiàn)類。此時ProviderMananger所處的情況大概就跟下圖一般困惑:
在ProviderMananger的視角里妙蔗,所有的Authentication實現(xiàn)類都不具名傲霸,它不僅不能通過自身完成驗證工作也不能獨立完成判斷是否支持的工作,而是統(tǒng)統(tǒng)交給AuthenticationProvider去完成。而不同的AuthenticationProvider開發(fā)初衷本就是為了支持指定的某種驗證協(xié)議昙啄,所以在特定的AuthenticationProvider的視角中穆役,他只關心當前Authentication是不是他預先設計處理的類型即可。
在使用用戶名和密碼的驗證場景中梳凛,驗證使用的用戶名和密碼被封裝成了UsernamePasswordAuthenticationToken對象耿币。Spring Security便為了向UsernamePasswordAuthenticationToken對象在核心層提供相關的驗證服務便繼承AuthenticationProvider開發(fā)了使用用戶名和密碼與UserDetailsService交互并且驗證密碼的。
DaoAuthenticationProvider是的實現(xiàn)類韧拒,DaoAuthenticationProvider針對UsernamePasswordAuthenticationToken的大部分邏輯都是通過AbstractUserDetailsAuthenticationProvider完成的淹接。比如針對ProviderManager詢問是否支持當前Authentication的supports方法:
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
可能有些同學對isAssignableFrom方法比較陌生,這是一個判斷兩個類之間是否存在繼承關系使用的判斷方法叛溢,DaoAuthenticationProvider會判斷當前的Authentication的實現(xiàn)類是否是UsernamePasswordAuthenticationToken它本身塑悼,或者是擴展了UsernamePasswordAuthenticationToken的子孫類。返回true的場景只有一種雇初,便是當前的Authentication是UsernamePasswordAuthenticationToken實現(xiàn)拢肆,換言之便是DaoAuthenticationProvider設計上需要進行處理的某種特定的驗證協(xié)議的信息載體的實現(xiàn)。
核心驗證邏輯:Authentication authenticate(Authentication authentication)
完成了是否支持的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返回最終的身份驗證結果與身份信息浓冒。這樣一個特定場景使用用戶名和密碼的驗證流程就完成了。
小結
我們先來總結下尖坤,當前出現(xiàn)過的針對用戶名和密碼擴展過的類與其為何被擴展的原因稳懒。
- UsernamePasswordAuthenticationFilter擴展AbstractAuthenticationProcessingFilter,因為需要從HTTP請求中從指定名稱的參數(shù)獲取用戶名和密碼慢味,并且傳遞給驗證核心场梆;
- UsernamePasswordAuthenticationToken擴展Authentication,因為我們設計了一套約定將用戶名和密碼放入了指定的屬性中以便核心讀取使用纯路;
- DaoAuthenticationProvider 擴展AuthenticationProvider或油,因為我們需要在核心中對UsernamePasswordAuthenticationToken進行處理,并按照約定讀出用戶名和密碼使其可以進行身份驗證操作驰唬。
結尾
本章的重點是介紹特定場景下框架是如何通過擴展指定組件來完成預設驗證邏輯的交互過程顶岸。其實整個驗證工作核心部分是在DaoAuthenticationProvider中進行完成的腔彰,但是這部分內(nèi)容涉及到具體的驗證協(xié)議的實現(xiàn)邏輯非常復雜,本期就暫時略過蜕琴,在一下期中我們將對驗證核心最重要的組件AuthenticationProvider其依賴的組件和對應職責做一個全面的講解萍桌。
我們下期再見。