Spring Security小教程 Vol 4. 使用用戶名和密碼驗證身份-UsernamePasswordAuthenticationFilter

前言

上一期我們分享了Spring Security是如何通過AbstractAuthenticationProcessingFilter向Web應用向基于HTTP攒岛、瀏覽器的請求提供身份驗證服務的。
這一次我們針對最常用裙士,也是Spring Security默認在HTTP上使用的驗證過濾器\color{red}{UsernamePasswordAuthenticationFilter}即基于用戶名和密碼的身份驗證過濾器是如何與核心進行交互進行展開說明。目的是希望讓大家對如何在Spring Security的核心上完成一個指定的身份驗證協(xié)議的擴展工作砂吞,已經(jīng)涉及相關主要組件及其角色職責有個初步的了解以现。
這一期的內(nèi)容如果有了前幾期對身份驗證核心的背景,相對來說比較的簡單坑资,因為整個流程就是在原有的基礎上更加具體化了場景:身份驗證的數(shù)據(jù)來源是用戶提交的請求,驗證的憑證是用戶名和密碼穆端。由于這樣的原因袱贮,這一期更像是對前幾期的一個綜合性的應用總結。

第四期 UsernamePasswordAuthenticationFilter詳細說明

本期的任務清單

  1. 了解UsernamePasswordAuthenticationFilter的職責和實現(xiàn)

1. 了解UsernamePasswordAuthenticationFilter的職責和實現(xiàn)

UsernamePasswordAuthenticationFilter類的說明

UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter針對使用用戶名和密碼進行身份驗證而定制化的一個過濾器体啰。
在一開始我們先通過下面的配圖來回憶一下我們的老朋友AbstractAuthenticationProcessingFilter的在框架中的角色與職責攒巍。


AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter在整個身份驗證的流程中主要處理的工作就是所有與Web資源相關的事情,并且將其封裝成Authentication對象荒勇,最后調(diào)用AuthenticationManager的驗證方法柒莉。所以UsernamePasswordAuthenticationFilter的工作大致也是如此,只不過在這個場景下更加明確了Authentication對象的封裝數(shù)據(jù)的來源和形式——使用用戶名和密碼沽翔。

接著我們再對\color{red}{UsernamePasswordAuthenticationFilter}的屬性和方法做一個快速的了解兢孝。UsernamePasswordAuthenticationFilter繼承擴展了AbstractAuthenticationProcessingFilter,相對與AbstractAuthenticationProcessingFilter而言主要有以下幾個改動:

  1. 屬性中增加了username和password字段仅偎;
  2. 強制的只對POST請求應用跨蟹;
  3. 重寫了attemptAuthentication身份驗證入口方法。
UsernamePasswordAuthenticationFilter的屬性中額外增加了username和password字段

封裝用戶名密碼的基石:UsernamePasswordAuthenticationToken

在UsernamePasswordAuthenticationFilter的屬性聲明中額外增加了username和password的動機很容易明白橘沥,即需要從HttpRequest中獲取對應的參數(shù)字段窗轩,并將其封裝進Authentication中傳遞給AuthenticationManager進行身份驗證。這里讓我們回顧下Authentication到底是什么座咆?Authentication是一個接口聲明痢艺,一個特定行為的聲明,它并不是一個類介陶,沒有辦法實例化為對象進行傳遞腹备。所以我們首先需要對Authentication進行實現(xiàn),使其可以被實例化斤蔓。

Authentication接口聲明

在UsernamePasswordAuthenticationFilter的身份驗證設計里,我們需要驗證協(xié)議用簡單的語言可以描述為:給我一組用戶名和密碼镀岛,如果匹配弦牡,那么就算驗證成功友驮。用戶名即是一個唯一可以標識不同用戶的字段,而密碼則是檢驗當前的身份驗證是否正確的憑證信息驾锰。在Spring Security中便將使用username和password封裝成Authentication的實現(xiàn)聲明為了
\color{red}{UsernamePasswordAuthenticationToken}
卸留。

UsernamePasswordAuthenticationToken繼承了\color{red}{AbstractAuthenticationToken}抽象類,其主要與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接口聲明

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)類\color{red}{ProviderManager}芦倒,在處理authenticate身份驗證入口方法的時,首先第一解決的問題便是:我手下哪個AuthenticationProvider能驗證當前傳入的Authentication不翩?為此ProviderManager便會對其所有的AuthenticationProvider做supports方法檢測兵扬,直到有AuthenticationProvider能在supports方法被調(diào)用后返回true。

我們了解了框架上的設計邏輯:先要知道知道誰能處理當前的身份驗證信息請求再要求它進行驗證工作口蝠。
回到我們的場景上來:UsernamePasswordAuthenticationFilter已經(jīng)封裝好了一個UsernamePasswordAuthenticationToken傳遞給了ProviderMananger器钟。緊接著當前ProviderMananger正焦頭爛額的詢問哪個AuthenticationProvider能支持這個Authentication的實現(xiàn)類。此時ProviderMananger所處的情況大概就跟下圖一般困惑:


ProviderMananger的煉獄生活

在ProviderMananger的視角里妙蔗,所有的Authentication實現(xiàn)類都不具名傲霸,它不僅不能通過自身完成驗證工作也不能獨立完成判斷是否支持的工作,而是統(tǒng)統(tǒng)交給AuthenticationProvider去完成。而不同的AuthenticationProvider開發(fā)初衷本就是為了支持指定的某種驗證協(xié)議昙啄,所以在特定的AuthenticationProvider的視角中穆役,他只關心當前Authentication是不是他預先設計處理的類型即可。
在使用用戶名和密碼的驗證場景中梳凛,驗證使用的用戶名和密碼被封裝成了UsernamePasswordAuthenticationToken對象耿币。Spring Security便為了向UsernamePasswordAuthenticationToken對象在核心層提供相關的驗證服務便繼承AuthenticationProvider開發(fā)了使用用戶名和密碼與UserDetailsService交互并且驗證密碼的\color{red}{DaoAuthenticationProvider}
DaoAuthenticationProvider是\color{red}{AbstractUserDetailsAuthenticationProvider}的實現(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)過的針對用戶名和密碼擴展過的類與其為何被擴展的原因稳懒。

  1. UsernamePasswordAuthenticationFilter擴展AbstractAuthenticationProcessingFilter,因為需要從HTTP請求中從指定名稱的參數(shù)獲取用戶名和密碼慢味,并且傳遞給驗證核心场梆;
  2. UsernamePasswordAuthenticationToken擴展Authentication,因為我們設計了一套約定將用戶名和密碼放入了指定的屬性中以便核心讀取使用纯路;
  3. DaoAuthenticationProvider 擴展AuthenticationProvider或油,因為我們需要在核心中對UsernamePasswordAuthenticationToken進行處理,并按照約定讀出用戶名和密碼使其可以進行身份驗證操作驰唬。
客制化驗證協(xié)議過程中涉及擴展的類

結尾

本章的重點是介紹特定場景下框架是如何通過擴展指定組件來完成預設驗證邏輯的交互過程顶岸。其實整個驗證工作核心部分是在DaoAuthenticationProvider中進行完成的腔彰,但是這部分內(nèi)容涉及到具體的驗證協(xié)議的實現(xiàn)邏輯非常復雜,本期就暫時略過蜕琴,在一下期中我們將對驗證核心最重要的組件AuthenticationProvider其依賴的組件和對應職責做一個全面的講解萍桌。
我們下期再見。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凌简,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恃逻,更是在濱河造成了極大的恐慌雏搂,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇损,死亡現(xiàn)場離奇詭異凸郑,居然都是意外死亡,警方通過查閱死者的電腦和手機矛市,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門芙沥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人漾抬,你說我怎么就攤上這事拐叉√迹” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵歌憨,是天一觀的道長。 經(jīng)常有香客問我墩衙,道長务嫡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任漆改,我火速辦了婚禮心铃,結果婚禮上,老公的妹妹穿的比我還像新娘挫剑。我一直安慰自己去扣,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布暮顺。 她就那樣靜靜地躺著厅篓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捶码。 梳的紋絲不亂的頭發(fā)上羽氮,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音惫恼,去河邊找鬼档押。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的令宿。 我是一名探鬼主播叼耙,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粒没!你這毒婦竟也來了筛婉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤癞松,失蹤者是張志新(化名)和其女友劉穎爽撒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體响蓉,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡硕勿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了枫甲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片源武。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖想幻,靈堂內(nèi)的尸體忽然破棺而出粱栖,到底是詐尸還是另有隱情,我是刑警寧澤举畸,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布查排,位于F島的核電站,受9級特大地震影響抄沮,放射性物質(zhì)發(fā)生泄漏跋核。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一叛买、第九天 我趴在偏房一處隱蔽的房頂上張望砂代。 院中可真熱鬧,春花似錦率挣、人聲如沸刻伊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捶箱。三九已至,卻和暖如春动漾,著一層夾襖步出監(jiān)牢的瞬間丁屎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工旱眯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晨川,地道東北人证九。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像共虑,于是被迫代替她去往敵國和親愧怜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容