咱們在某一期其實已經對Authentication
身份驗證中的主要組件進行過介紹,而且經過幾期的分享讓你們大體瞭解了Web應用大體利用核心進行身份驗證的流程和相關的擴展點。 這一期咱們用一期的篇章把關注點放在Authentication
身份驗證核心最主要的幾個服務AuthenticationMananger
、AuthenticationProvider
和UserDetailsService
,三個頂層接口進行展開。經過Spring Security對這些接口服務的實現進行說明講解。目的是爲了未來客製化擴展核心服務作好知識儲備。算法
本期的重點是Authentication身份驗證幾個核心服務接口:sql
額外的還包括兩個負責封裝用戶身份信息的接口與類:數據庫
咱們回顧下前幾期咱們在分享Spring Security核心組件時候曾用到過如下這張圖比較重要的核心組件: bash
這一期的重點顯而易見即是紅框中身份驗證部分的三個核心組件以及其相關的組件。網絡
AuthenticationMananger
做爲整個身份驗證核心最外層的封裝負責與外部使用者進行交互。 AuthenticationMananger
接口有且僅有一個對外的服務即是「身份驗證」。這樣是整個身份驗證服務對外提供的服務接口。數據結構
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
複製代碼
外部使用者經過將身份驗證的必要信息,好比用戶名和密碼封裝一個Authentication傳遞、調用AuthenticationMananger的authenticate方法。若是沒有返回異常和null值,那麼驗證服務即是完成。完成身份驗證的Authentication不只包含了用戶的身份驗證信息,好比用戶名,額外還會將該用戶身份下全部對應的權限列表也一併封裝返回。 框架
在整個與外部使用交互的過程當中Authentication
的職責有兩個,第一個是封裝了驗證請求的參數,第二個即是封裝了用戶的權限信息。結合Authentication
的接口設計便更加清晰了這樣的設計意圖:principal用於存放用戶的身份標識信息,好比用戶名,credentials用於存放用戶的驗證憑證好比密碼,authorities用於存放用戶的權限列表。而details則存放了除了用戶名和密碼其餘可能會被用於身份驗證的信息,好比應用限定用戶的使用ip範圍場景下,ip信息可能便會被存放在details作輔助的驗證信息使用。 ide
爲了向外部提供身份驗證服務,Spring Security中經過ProviderMananger
實現了AuthenticationManager
的身份驗證接口。做爲實現類ProviderMananger
便不能和AuthenticationManager
同樣只關心惟一的抽象核心服務authenticate。在ProviderMananger
爲了管理外部輸入與像外部返回的Authentication
,ProviderMananger
內部大體的工序以下:ui
Authentication
形式的AuthenticationProvider
;若是自身的providers中沒法處理驗證而且當前層次的Mananger
還有父級的Mananger
則向上傳遞,交由父層Mananger
進行處理;Authentication
並不會從持久化或者其餘數據源中攜帶,在返回前將details寫入返回給外部的Authentication
;ProviderMananger
完成的三件工做,大體明白了雖然整個驗證框架只有一個ProviderManager
暴露在外部,可是其內部多是有多個AuthenticationMananger
和AuthenticationProvider
組成的網絡,而且最終進行核心身份驗證的仍是AuthenticationProvider
。核心在葉子節點中依次尋找對驗證當前Authentication
形式的AuthenticationProvider
。若是存在支持便將驗證請求的Authentication
傳遞給AuthenticationProvider
,委託其進行驗證。在處理輸入的驗證請求Authentication
,ProviderMananger
並不對其進行任何的處理,而是指在處理完後進行必要的加工和處理。
相對AuthenticationMananger而言AuthenticationProvider的工做更加明確:針對特定的驗證數據,提供特定的驗證行爲。在這個語境下,Authentication的設計目的是解決驗證什麼(What)的問題,而authenticate方法更像是在回答怎麼驗證的問題(How)。加密
那麼咱們先對驗證數據也就是Authentication的設計進行展開討論。Authentication主要職責就是封裝身份驗證時候須要的信息數據,好比用戶名場景下的用戶名和密碼,短信驗證碼下的手機號碼和驗證碼,OAuth2場景下的ID和Code。總之每一個不一樣驗證協議使用的驗證信息都須要被被封裝成Authentication,更準確說在Spring Security把這種封裝了用戶身份驗證信息的Authentication具體爲了的概念,畢竟一說token更容易理解。全部Spring Security中提供的各類協議的身份驗證數據的封裝都繼承
,基於用戶名和密碼的UsernamePasswordAuthenticationToken,基於OAuth2的OAuth2AuthorizationCodeAuthenticationToken,基於CAS的CasAssertionAuthenticationToken。 一般咱們使用用戶名和密碼的場景是最多,不管是使用基於數據庫持久化的用戶名密碼方案仍是基於LDAP的用戶名和密碼方法。雖然驗證在驗證協實現有細微差異,可是不管使用驗證LDAP仍是數據庫進行身份驗證比對,由於用戶提交的驗證身份信息幾乎一致,咱們即可以複用通用結構的AuthenticationToken——將username賦值到principal屬性並將password賦值到cencredentials屬性中。這樣就意味着咱們在AuthenticationToken設計上最須要考慮是數據的封裝,而不是身份驗證行爲的實現。
咱們已經解決了第一個問題,在AuthenticationProvider驗證數據放均可以經過傳入Authentication的各類實現類AuthenticationToken進行獲取。下一個問題即是AuthenticationProvider是如何進行身份驗證的。 咱們假設的場景是須要對使用用戶名和密碼的UsernamePasswordAuthenticationToken進行驗證。 在Spring Security針對UsernamePasswordAuthenticationToken進行身份驗證的有主要有兩個AuthenticationProvider:一個是基於Dao模型與數據層用戶信息對比驗證的DaoAuthenticationProvider,另一個是雖然一樣使用用戶名和密碼,可是驗證流程更加複雜,且用戶數據是經過與LDAP服務進行用戶驗證的LdapAuthenticationProvider。在這裏使用最普遍使用的基於Dao的DaoAuthenticationProvider進行說明,若是有對Ldap實現有興趣的相信在看完對DaoAuthenticationProvider的分析以後再閱讀LdapAuthenticationProvider部分的代碼就會輕鬆許多。
DaoAuthenticationProvider爲了實現外部的驗證請求便須要對外部傳遞身份信息——用戶名和密碼進行驗證。咱們把這個任務進一步分解成兩個獨立的任務:
爲何在DaoAuthenticationProvider會將驗證任務再分解成這兩個獨立任務,最大緣由即是,這兩個任務一個感知外部資源,另外一個感知驗證算法,兩種都是不一樣用戶可能存在不一樣的使用場景,框架並沒有法控制具體的實現。 第一個任務,從數據層獲取對應用戶名在數據層的數據記錄,咱們的目標是從數據層中查找到咱們須要比對的用戶身份數據,可是在這個場景下咱們無非控制的是數據層的實現具體是什麼?是經過JDBC訪問Mysql仍是經過JPA訪問Oracle,更或是直接經過內存訪問一個存儲了用戶信息鍵值對的Map? 第二個任務,對外部的用戶名、密碼與數據層的用戶名、密碼進行比對,具體的加密算法是什麼?如何實現的? 這兩個問題在DaoAuthenticationProvider中都沒法給出明確的實現。Spring Security便將這兩種在DaoAuthenticationProvider沒法肯定、存在變化的行爲分別委託給了DaoAuthenticationProvider兩個重要組件去完成:
UserDetailsService接口定位從他的接口方法就能夠明白,就是向核心組件們提供數據層的用戶信息,而用戶信息在這裏被封裝成了UserDetails。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
複製代碼
當咱們肯定咱們獲取用戶身份信息的方法以後,咱們即可以自行擴展UserDetailsService方法,告知框架如何獲取用戶身份信息。一般這個步驟是使用Spring Security中是必須完成的工做。 咱們在第一個章節中曾經寫過如下代碼用於配置咱們使用的UserDetailsService:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//注入新的UserDetailsServiceBean
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
return manager;
}
}
複製代碼
那一次咱們使用了基於內存鍵值對的形式來存儲和獲取用戶信息記錄。一樣的咱們也能夠經過JDBC和JPA來獲取用戶身份信息,而Spring Security很貼心的已經提供了一個基於JDBC的UserServiceDetails實現和對應的模板DDL:
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
複製代碼
而經過UserDetailsService返回的數據類型是UserDetails,UserDetails中封裝了用戶名、密碼和受權信息同時還額外包括了一些過時和鎖定的標識屬性。咱們不難發現UserDetails封裝的數據和Authentication很是的類似。沒錯,在身份驗證成功後,DaoAuthenticationProvider便會將內部的UserDetails抽離必要的數據對應賦值到UsernamePasswordAuthenticationToken最終返回給外部調用者進行使用。咱們能夠簡單的把UserDetails理解爲用戶身份信息在數據層的封裝。在客製化的過程當中,若是用戶信息的數據結構是比較特殊的結構,好比Ldap,那麼即可以自行擴展UserDetails客製化一個特殊的結構用於獲取用戶數據記錄。 說到這裏基本上咱們已經瞭解了DaoAuthenticationProvider兩個組件中用於獲取用戶身份記錄的UserDetailsService部分,下面咱們介紹下處理密碼驗證的PasswordEncoder部分。
咱們繼續追蹤上面的場景來講下關於驗證算法部分。DaoAuthenticationProvider收到了外部提交的用戶名和密碼,一樣的DaoAuthenticationProvider也查找到了對應用戶名在數據庫中的用戶名和密碼。一般狀況下雖然都是密碼,數據庫中存儲的密碼一般會進行過必定的加密。DaoAuthenticationProvider便須要將外部提交的用戶名和密碼進行一次加密流程並進行比對。舉個例子咱們當前使用的算法好比是MD5,加密明文的樣本是用戶的用戶名拼接用戶名。若有當前須要身份驗證的請求中用戶名是admin,密碼是password。一樣的在數據庫中加密後的密碼是9b02edfbc208a538。咱們便須要對外部傳遞的用戶名和密碼作一個MD5("passwordadmin")獲得9b02edfbc208a538,再與數據庫中的password字段進行對比,若是一致則認定驗證成功。 在Spring Security中這種針對處理稱爲,PasswordEncoder接口主要的做用就是對明文密碼進行加密與比對。
Spring Security中默認向DaoAuthenticationProvider提供的PasswordEncoder是BCryptPasswordEncoder。若是對BCrypt能夠額外經過谷歌去了解加密流程。 若是咱們須要客製化本身的加密算法,只要實現PasswordEncoder接口,並從新經過Spring注入DaoAuthenticationProvider即可以了。
@Bean
public PasswordEncoder passwordEncoder() {
//經過修改注入的實例,客製化本身的PasswordEncoder
return new BCryptPasswordEncoder();
}
複製代碼
PasswordEncoder 部分的功能相對較少,一般狀況下使用默認提供的BCryptPasswordEncoder就足夠完成任務。
在本期咱們花了很大篇幅介紹了身份驗證核心中最主要個幾個組件和基於一個使用用戶名和密碼驗證場景下對應接口的實現類的具體職責:
經過幾期的說明,整個Spring Security關於身份驗證的組件、流程和特定場景的實現基本咱們都瞭解了一遍。從下一期開始,咱們會開始講解訪問控制部分、框架配置部分的設計與概念。同時也將不按期經過一些場景的實戰強相關框架概念和設計理念。 謝謝你們,咱們下期再見。