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

前言

上一期咱們分享了Spring Security是如何經過AbstractAuthenticationProcessingFilter向Web應用向基於HTTP、瀏覽器的請求提供身份驗證服務的。 這一次咱們針對最經常使用,也是Spring Security默認在HTTP上使用的驗證過濾器UsernamePasswordAuthenticationFilter即基於用戶名和密碼的身份驗證過濾器是如何與核心進行交互進行展開說明。目的是但願讓你們對如何在Spring Security的核心上完成一個指定的身份驗證協議的擴展工做,已經涉及相關主要組件及其角色職責有個初步的瞭解。 這一期的內容若是有了前幾期對身份驗證核心的背景,相對來講比較的簡單,由於整個流程就是在原有的基礎上更加具體化了場景:身份驗證的數據來源是用戶提交的請求,驗證的憑證是用戶名和密碼。因爲這樣的緣由,這一期更像是對前幾期的一個綜合性的應用總結。java

第四期 UsernamePasswordAuthenticationFilter

本期的任務清單

  1. Spring Security向Web應用提供支持的相關組件
  2. 瞭解UsernamePasswordAuthenticationFilter的職責和實現
  3. 瞭解UsernamePasswordAuthenticationToken封裝的身份驗證信息

一. UsernamePasswordAuthenticationFilter

1.1 角色與職責

UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter針對使用用戶名和密碼進行身份驗證而定製化的一個過濾器。 在一開始咱們先經過下面的配圖來回憶一下咱們的老朋友AbstractAuthenticationProcessingFilter的在框架中的角色與職責。 瀏覽器

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter在整個身份驗證的流程中主要處理的工做就是全部與Web資源相關的事情,而且將其封裝成Authentication對象,最後調用AuthenticationManager的驗證方法。以UsernamePasswordAuthenticationFilter的工做大體也是如此,只不過在這個場景下更加明確了Authentication對象的封裝數據的來源和形式:使用用戶名和密碼。bash

1.2 屬性與方法

接着咱們再UsernamePasswordAuthenticationFilter的屬性和方法作一個快速的瞭解。 UsernamePasswordAuthenticationFilter繼承擴展了AbstractAuthenticationProcessingFilter,相對與AbstractAuthenticationProcessingFilter而言主要有如下幾個改動:框架

UsernamePasswordAuthenticationFilter的屬性中額外增長了username和password字段

  1. 屬性中增長了username和password字段;
  2. 強制的只對POST請求應用;
  3. 重寫了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

2.1. 封裝驗證請求數據的載體

UsernamePasswordAuthenticationToken是整個身份驗證流程封裝了身份驗證請求數據中的數據對象。 在UsernamePasswordAuthenticationFilter的屬性聲明中額外增長了username和password的動機很容易明白,即須要從HttpRequest中獲取對應的參數字段,並將其封裝進Authentication中傳遞給AuthenticationManager進行身份驗證這裏讓咱們回顧下Authentication究竟是什麼?Authentication是一個接口聲明,一個特定行爲的聲明,它並非一個類,沒有辦法實例化爲對象進行傳遞。因此咱們首先須要對Authentication進行實現,使其能夠被實例化。 this

Authentication接口聲明

UsernamePasswordAuthenticationFilter的身份驗證設計裏,咱們須要驗證協議用簡單的語言能夠描述爲:給我一組用戶名和密碼,若是匹配,那麼就算驗證成功。用戶名便是一個惟一能夠標識不一樣用戶的字段,而密碼則是檢驗當前的身份驗證是否正確的憑證信息。在Spring Security中便將使用username和password封裝成Authentication的實現聲明爲了UsernamePasswordAuthenticationTokenspa

2.2. 封裝用戶名和密碼

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

三. 驗證核心的工做者:AuthenticationProvider

Web層的工做已經完成了,Authentication接口的實現類UsernamePasswordAuthenticationToken經過AuthenticationManager提供的驗證方法做爲參數被傳遞到了身份驗證的核心組件中。 咱們曾屢次強調過一個設計概念:AuthenticationManager接口設計上並非用於完成特定的身份驗證工做的,而是調用其所配發的AuthenticationProvider接口去實現的。 那麼這裏就有一個疑問,針對接口聲明參數聲明的Authentication,針對不一樣驗證協議的AuthenticationProvider的實現類們是完成對應的工做的,而且AuthenticationManager是如何知道應該使用哪個AuthenticationProvider才能完成對應協議的驗證工做? 那麼咱們首先先複習下驗證核心的大明星AuthenticationProvider接口的聲明:

AuthenticationProvider接口聲明
AuthenticationProvider只包含兩個方法聲明:

一個是用於驗證身份請求AuthenticationToken的authenticate方法:

Authentication authenticate(Authentication authentication) throws AuthenticationException;
複製代碼

另一個即是讓AuthenticationManager能夠經過調用該方法辨別當前AuthenticationProvider是不是完成相應驗證工做的supports方法:

boolean supports(Class<?> authentication);
複製代碼

對於AuthenticationProvider整個體系能說的很是多,本期只對咱們「須要瞭解」的AuthenticationProvider中兩個接口聲明的方法作個最簡單的說明。其餘部分在之後單獨對AuthenticationProvider體系介紹的時候再進一步展開。

3.1 第一個方法- supports

在Spring Security中惟一AuthenticationManager的實現類ProviderManager,在處理authenticate身份驗證入口方法的時,首先第一解決的問題即是:我手下哪一個AuthenticationProvider能驗證當前傳入的Authentication?爲此ProviderManager便會對其全部的AuthenticationProvider作supports方法檢測,直到有AuthenticationProvider能在supports方法被調用後返回true。

咱們瞭解了框架上的設計邏輯:先要知道知道誰能處理當前的身份驗證信息請求再要求它進行驗證工做。 回到咱們的場景上來:UsernamePasswordAuthenticationFilter已經封裝好了一個UsernamePasswordAuthenticationToken,並將它傳遞給了ProviderMananger。隨後ProviderMananger便會一次輪訓它管理的全部AuthenticationProvider,詢問是否有誰能支持這個Authentication的實現類。此時ProviderMananger所處的狀況大概就跟下圖通常困惑:

ProviderMananger的煉獄生活

在ProviderMananger的視角里,全部的Authentication實現類都不具名,它不只不能經過自身完成驗證工做也不能獨立完成判斷是否支持的工做,而是通通交給AuthenticationProvider去完成。而不一樣的AuthenticationProvider開發初衷本就是爲了支持指定的某種驗證協議,因此在特定的AuthenticationProvider的視角中,他只關心當前Authentication是否是他預先設計處理的類型便可。 在使用用戶名和密碼的驗證場景中,驗證使用的用戶名和密碼被封裝成了UsernamePasswordAuthenticationToken對象。Spring Security便爲了向UsernamePasswordAuthenticationToken對象在覈心層提供相關的驗證服務便繼承AuthenticationProvider開發了使用用戶名和密碼與UserDetailsService交互而且驗證密碼的DaoAuthenticationProviderDaoAuthenticationProvider是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設計上須要進行處理的某種特定的驗證協議的信息載體的實現。

3.2 第二個方法- authenticate

完成了是否支持的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返回最終的身份驗證結果與身份信息。這樣一個特定場景使用用戶名和密碼的驗證流程就完成了。

小結

咱們先來總結下,當前出現過的針對用戶名和密碼擴展過的類與其爲什麼被擴展的緣由。

  1. UsernamePasswordAuthenticationFilter擴展AbstractAuthenticationProcessingFilter,由於須要從HTTP請求中從指定名稱的參數獲取用戶名和密碼,而且傳遞給驗證核心;
  2. UsernamePasswordAuthenticationToken擴展Authentication,由於咱們設計了一套約定將用戶名和密碼放入了指定的屬性中以便核心讀取使用;
  3. DaoAuthenticationProvider 擴展AuthenticationProvider,由於咱們須要在覈心中對UsernamePasswordAuthenticationToken進行處理,並按照約定讀出用戶名和密碼使其能夠進行身份驗證操做。

客製化驗證協議過程當中涉及擴展的類

結尾

本章的重點是介紹特定場景下框架是如何經過擴展指定組件來完成預設驗證邏輯的交互過程。其實整個驗證工做核心部分是在DaoAuthenticationProvider中進行完成的,可是這部份內容涉及到具體的驗證協議的實現邏輯很是複雜,本期就暫時略過,在一下期中咱們將對驗證核心最重要的組件AuthenticationProvider其依賴的組件和對應職責作一個全面的講解。 咱們下期再見。

相關文章
相關標籤/搜索