Spring Security如何優(yōu)雅的增加OAuth2協(xié)議授權模式
轉自:陶陶技術筆記,作者:zlt2000
一、什么是OAuth2協(xié)議?
OAuth 2.0 是一個關于授權的開放的網(wǎng)絡協(xié)議,是目前最流行的授權機制。
數(shù)據(jù)的所有者告訴系統(tǒng),同意授權第三方應用進入系統(tǒng),獲取這些數(shù)據(jù)。系統(tǒng)從而產(chǎn)生一個短期的進入令牌(token),用來代替密碼,供第三方應用使用。
由于授權的場景眾多,OAuth 2.0 協(xié)議定義了獲取令牌的四種授權方式,分別是:
「授權碼模式」:授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后臺服務器,與"服務提供商"的認證服務器進行互動。
「簡化模式」:簡化模式(implicit grant type)不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"授權碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證。
「密碼模式」:密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼??蛻舳耸褂眠@些信息,向"服務商提供商"索要授權。
「客戶端模式」:客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式并不屬于OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。
四種授權模式分別使用不同的
grant_type來區(qū)分
?友情推薦下歡哥的開源項目:https://github.com/yinjihuan/kitty
Spring Cloud & Spring Cloud Alibaba 基礎框架,內置了 Cat 監(jiān)控,互聯(lián)網(wǎng)公司落地 Spring Cloud 架構必備。
二、為什么要自定義授權類型?
雖然 OAuth2 協(xié)議定義了4種標準的授權模式,但是在實際開發(fā)過程中還是遠遠滿足不了各種變態(tài)的業(yè)務場景,需要我們去擴展。
例如增加圖形驗證碼、手機驗證碼、手機號密碼登錄等等的場景
而常見的做法都是通過增加 過濾器Filter 的方式來擴展 Spring Security 授權,但是這樣的實現(xiàn)方式有兩個問題:
脫離了 OAuth2的管理不靈活:例如系統(tǒng)使用 「密碼模式」 授權,網(wǎng)頁版需要增加圖形驗證碼校驗,但是手機端APP又不需要的情況下,使用增加 Filter的方式去實現(xiàn)就比較麻煩了。
?
所以目前在 Spring Security 中比較優(yōu)雅和靈活的擴展方式就是通過自定義 「grant_type」 來增加授權模式。
?
三、實現(xiàn)思路
在擴展之前首先需要先了解 Spring Security 的整個授權流程,我以 「密碼模式」 為例去展開分析,如下圖所示

3.1. 流程分析
整個授權流程關鍵點分為以下兩個部分:
「第一部分」:關于授權類型 grant_type 的解析
每種 grant_type都會有一個對應的TokenGranter實現(xiàn)類。所有 TokenGranter實現(xiàn)類都通過CompositeTokenGranter中的tokenGranters集合存起來。然后通過判斷 grantType參數(shù)來定位具體使用那個TokenGranter實現(xiàn)類來處理授權。
?
「第二部分」:關于授權登錄邏輯
每種 授權方式都會有一個對應的AuthenticationProvider實現(xiàn)類來實現(xiàn)。所有 AuthenticationProvider實現(xiàn)類都通過ProviderManager中的providers集合存起來。TokenGranter類會 new 一個AuthenticationToken實現(xiàn)類,如UsernamePasswordAuthenticationToken傳給ProviderManager類。而 ProviderManager則通過AuthenticationToken來判斷具體使用那個AuthenticationProvider實現(xiàn)類來處理授權。具體的登錄邏輯由 AuthenticationProvider實現(xiàn)類來實現(xiàn),如DaoAuthenticationProvider。
?
3.2. 擴展分析
根據(jù)上面的流程,擴展分為以下兩種場景
「場景一」:只對原有的授權邏輯進行增強或者擴展,如:用戶名密碼登錄前增加圖形驗證碼校驗。
該場景需要定義一個新的 grantType 類型,并新增對應的 TokenGranter 實現(xiàn)類 「添加擴展內容」,然后加到 CompositeTokenGranter 中的 tokenGranters 集合里即可。
參考代碼:https://gitee.com/zlt2000/microservices-platform/blob/master/zlt-uaa/src/main/java/com/central/oauth/granter/PwdImgCodeGranter.java
?
「場景二」:新加一種授權方式,如:手機號加密碼登錄。
該場景需要實現(xiàn)以下內容:
定義一個新的 grantType類型,并新增對應的TokenGranter實現(xiàn)類加到CompositeTokenGranter中的tokenGranters集合里新增一個 AuthenticationToken實現(xiàn)類,用于存放該授權所需的信息。新增一個 AuthenticationProvider實現(xiàn)類 「實現(xiàn)授權的邏輯」,并重寫supports方法綁定步驟二的AuthenticationToken實現(xiàn)類
參考代碼:https://gitee.com/zlt2000/microservices-platform/blob/master/zlt-uaa/src/main/java/com/central/oauth/granter/MobilePwdGranter.java
?
四、代碼實現(xiàn)
下面以「場景二」新增手機號加密碼授權方式為例,展示核心的代碼實現(xiàn)
4.1. 創(chuàng)建 AuthenticationToken
創(chuàng)建 MobileAuthenticationToken 類,用于存儲手機號和密碼信息
public?class?MobileAuthenticationToken?extends?AbstractAuthenticationToken?{
?private?static?final?long?serialVersionUID?=?SpringSecurityCoreVersion.SERIAL_VERSION_UID;
?private?final?Object?principal;
?private?Object?credentials;
?public?MobileAuthenticationToken(String?mobile,?String?password)?{
??super(null);
??this.principal?=?mobile;
??this.credentials?=?password;
??setAuthenticated(false);
?}
?public?MobileAuthenticationToken(Object?principal,?Object?credentials,
??????????Collection?extends?GrantedAuthority>?authorities)?{
??super(authorities);
??this.principal?=?principal;
??this.credentials?=?credentials;
??super.setAuthenticated(true);
?}
?@Override
?public?Object?getCredentials()?{
??return?this.credentials;
?}
?@Override
?public?Object?getPrincipal()?{
??return?this.principal;
?}
?@Override
?public?void?setAuthenticated(boolean?isAuthenticated)?{
??if?(isAuthenticated)?{
???throw?new?IllegalArgumentException(
?????"Cannot?set?this?token?to?trusted?-?use?constructor?which?takes?a?GrantedAuthority?list?instead");
??}
??super.setAuthenticated(false);
?}
?@Override
?public?void?eraseCredentials()?{
??super.eraseCredentials();
?}
}
?
4.2. 創(chuàng)建 AuthenticationProvider
創(chuàng)建 MobileAuthenticationProvider 類,實現(xiàn)登錄邏輯,并綁定?MobileAuthenticationToken?類
@Setter
public?class?MobileAuthenticationProvider?implements?AuthenticationProvider?{
????private?ZltUserDetailsService?userDetailsService;
????private?PasswordEncoder?passwordEncoder;
????@Override
????public?Authentication?authenticate(Authentication?authentication)?{
????????MobileAuthenticationToken?authenticationToken?=?(MobileAuthenticationToken)?authentication;
????????String?mobile?=?(String)?authenticationToken.getPrincipal();
????????String?password?=?(String)?authenticationToken.getCredentials();
????????UserDetails?user?=?userDetailsService.loadUserByMobile(mobile);
????????if?(user?==?null)?{
????????????throw?new?InternalAuthenticationServiceException("手機號或密碼錯誤");
????????}
????????if?(!passwordEncoder.matches(password,?user.getPassword()))?{
????????????throw?new?BadCredentialsException("手機號或密碼錯誤");
????????}
????????MobileAuthenticationToken?authenticationResult?=?new?MobileAuthenticationToken(user,?password,?user.getAuthorities());
????????authenticationResult.setDetails(authenticationToken.getDetails());
????????return?authenticationResult;
????}
????@Override
????public?boolean?supports(Class>?authentication)?{
????????return?MobileAuthenticationToken.class.isAssignableFrom(authentication);
????}
}
?
4.3. 創(chuàng)建 TokenGranter
創(chuàng)建 MobilePwdGranter 類并定義 grant_type 的值為 mobile_password
public?class?MobilePwdGranter?extends?AbstractTokenGranter?{
????private?static?final?String?GRANT_TYPE?=?"mobile_password";
????private?final?AuthenticationManager?authenticationManager;
????public?MobilePwdGranter(AuthenticationManager?authenticationManager,?AuthorizationServerTokenServices?tokenServices
????????????,?ClientDetailsService?clientDetailsService,?OAuth2RequestFactory?requestFactory)?{
????????super(tokenServices,?clientDetailsService,?requestFactory,?GRANT_TYPE);
????????this.authenticationManager?=?authenticationManager;
????}
????@Override
????protected?OAuth2Authentication?getOAuth2Authentication(ClientDetails?client,?TokenRequest?tokenRequest)?{
????????Map?parameters?=?new?LinkedHashMap<>(tokenRequest.getRequestParameters());
????????String?mobile?=?parameters.get("mobile");
????????String?password?=?parameters.get("password");
????????parameters.remove("password");
????????Authentication?userAuth?=?new?MobileAuthenticationToken(mobile,?password);
????????((AbstractAuthenticationToken)?userAuth).setDetails(parameters);
????????userAuth?=?authenticationManager.authenticate(userAuth);
????????if?(userAuth?==?null?||?!userAuth.isAuthenticated())?{
????????????throw?new?InvalidGrantException("Could?not?authenticate?mobile:?"?+?mobile);
????????}
????????OAuth2Request?storedOAuth2Request?=?getRequestFactory().createOAuth2Request(client,?tokenRequest);
????????return?new?OAuth2Authentication(storedOAuth2Request,?userAuth);
????}
}
?
4.4. 加到 CompositeTokenGranter 中的集合里
//?添加手機號加密碼授權模式
tokenGranters.add(new?MobilePwdGranter(authenticationManager,?tokenServices,?clientDetailsService,?requestFactory));
?
4.5. 測試
使用以下地址,指定 grant_type 為 mobile_password 進行授權,獲取?「access_token」
/oauth/token?grant_type=mobile_password&mobile={mobile}&password={password}

?
五、參考樣例
詳細的代碼實現(xiàn)可以參考
https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-uaa
我整理了一份很全的學習資料,感興趣的可以微信搜索「猿天地」,回復關鍵字 「學習資料」獲取我整理好了的 Spring Cloud,Spring Cloud Alibaba,Sharding-JDBC 分庫分表,任務調度框架 XXL-JOB,MongoDB,爬蟲等相關資料。
后臺回復?學習資料?領取學習視頻
如有收獲,點個在看,誠摯感謝
