Spring security OAuth2 深入解析
一、OAuth2 概要
1.1.OAuth2基本流程
話不多說,先上圖:
分析一波:
- client:第三方應用(即App或向外提供介面)
- Resource Owner:資源所有者(即使用者)
- Authentication Server:授權認證服務(發配Access Token)
- Resource Server:資源伺服器(儲存使用者資源資訊等資源)
其實不管微信或者QQ大體上都是使用這種OAuth2的基本流程:
- 第三方應用請求使用者授權;
- 使用者同意授權,並返回一個授權碼(code);
- 第三方應用根據授權碼(code)向授權認證服務進行授權;
- 授權伺服器根據授權碼(code),校驗通過,並返回給第三方應用令牌(Access Token);
- 第三方應用根據令牌(Access Token)向資源服務請求相關資源;
- 資源伺服器驗證令牌(Access Token),校驗通過,並返回第三方所請求的資源。
1.2.服務型別
OAuth2 在服務提供者上可分為兩類:
- 授權認證服務:AuthenticationServer
@Configuration
@EnableAuthorizationServer
public class CustomAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter
複製程式碼
- 資源獲取服務:ResourceServer
@Configuration
@EnableResourceServer
public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
複製程式碼
注:這兩者有時候可能存在同一個應用程式中(即SOA架構)。在Spring OAuth中可以簡便的將其分配到兩個應用中(即微服務),而且可多個資源獲取服務共享一個授權認證服務。
1.3.授權認證服務
主要的操作:
- 獲取第三方應用傳送的授權碼(code)以及第三方應用標識
- 根據授權碼及標識進行校驗
- 校驗通過,傳送令牌(Access Token)
分析一波:
1)第一步操作:
- 授權碼(code):第三方應用進行第一步“Authorization Request”時,請求引數redirect_uri中的回撥連結,服務會生成相關使用者憑證,並在其回撥連結上附帶code
- 第三方使用者標識:
- client_id: 第三方使用者的id(可理解為賬號)
- client_secret:第三方應用和授權伺服器之間的安全憑證(可理解為密碼)
注:其中client_id和client_secret都是授權伺服器傳送給第三方應用的,如:微信等一系列授權,在其平臺上註冊,獲取其appid和secret同樣道理(個人理解為賬號密碼)。
既然是賬號祕密,總不能以get請求,也太不安全了。因此,OAuth2要求該請求必須是POST請求,同時,還必須時HTTPS服務,以此保證獲取到的安全憑證(Access Token)的安全性。
2)第二步操作:
- 授權認證伺服器根據標識校驗第三方應用的真實性
- 授權認證伺服器根據授權碼(code)進行校驗使用者憑證
3)第三步操作:
- 生成Access Token(MD5型別,uuid型別,jwt型別等)
1.4.資源獲取服務
主要的操作:
- 校驗Access Token
- 發放資源資訊
二、Spring Security OAuth2的使用
1.授權認證服務
spring OAuth2中,我們配置一個授權認證服務,我們最主要有以下三點:
- 第三方使用者客戶端詳情 → Client
- 令牌的生成管理 → Access Token
- 端點接入 → endpoints
spring中有三個配置與這三點一一對應:
- ClientDetailsServiceConfigurer:用來配置客戶端詳情服務。
- AuthorizationServerSecurityConfigurer:用來配置令牌端點(Token Endpoint)的安全約束.
- AuthorizationServerEndpointsConfigurer:來配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)
1.1.第三方使用者客戶端詳情
除了上面說到的client_id和client_secret,還需要一些服務附帶一些授權認證引數。
1).Grant Type
其實OAuth2不僅提供授權碼(code)這種格式授權方式,還提供幾個其他型別。其中用Grant Type代表當前授權的型別。 Grant Type包括:
- authorization_code:傳統的授權碼模式
- implicit:隱式授權模式
- password:資源所有者(即使用者)密碼模式
- client_credentials:客戶端憑據(客戶端ID以及Key)模式
- refresh_token:獲取access token時附帶的用於重新整理新的token模式
2).scope
其實授權賦予第三方使用者可以在資源伺服器獲取資源,經常就是調取Api請求附帶令牌,然而調取api有增刪查改等功能,而scopes的值就是all(全部許可權),read,write等許可權。就是第三方訪問資源的一個許可權,訪問範圍。
3).accessTokenValiditySeconds
還可以設定accessTokenValiditySeconds屬性來設定Access Token的存活時間。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("catalpaFlat")
.secret("catalpaFlat-secret")
.accessTokenValiditySeconds(7200)
.authorizedGrantTypes("refresh_token","password")
.scopes("all");
}
複製程式碼
1.2.令牌的生成和管理
AccessToken的存在意義:
- 建立AccessToken,並儲存,以備後續請求訪問都可以認證成功並獲取到資源
- AccessToken還有一個潛在功能,就是使用jwt生成token時候,可以用來載入一些資訊,把一些相關許可權等包含在AccessToken中
1).AuthorizationServerTokenServices
AuthorizationServerTokenServices 提供了對AccessToken的相關操作建立、重新整理、獲取。
public interface AuthorizationServerTokenServices {
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}
複製程式碼
2).DefaultTokenServices
AuthorizationServerTokenServices竟然可以操作AccessToken,那麼OAuth2就預設為我們提供了一個預設的DefaultTokenServices。包含了一些有用實現,可以使用它來修改令牌的格式和令牌的儲存等,但是生成的token是隨機數。
3).TokenStore
建立AccessToken完之後,除了發放給第三方,肯定還得儲存起來,才可以使用。因此,TokenStore為我們完成這一操作,將令牌(AccessToken)儲存或持久化。
TokenStore也有一個預設的實現類InMemoryTokenStore,從名字就知道是通過儲存到記憶體進而實現儲存Access Token。
TokenStore的實現有多種型別,可以根據業務需求更改Access Token的儲存型別:
- InMemoryTokenStore:這個是OAuth2預設採用的實現方式。在單服務上可以體現出很好特效(即併發量不大,並且它在失敗的時候不會進行備份),大多專案都可以採用此方法。畢竟存在記憶體,而不是磁碟中,除錯簡易。
- JdbcTokenStore:這個是基於JDBC的實現,令牌(Access Token)會儲存到資料庫。這個方式,可以在多個服務之間實現令牌共享。
- JwtTokenStore:jwt全稱 JSON Web Token。這個實現方式不用管如何進行儲存(記憶體或磁碟),因為它可以把相關資訊資料編碼存放在令牌裡。JwtTokenStore 不會儲存任何資料,但是它在轉換令牌值以及授權資訊方面與 DefaultTokenServices 所扮演的角色是一樣的。但有兩個缺點:
- 撤銷一個已經授權的令牌會很困難,因此只適用於處理一個生命週期較短的以及撤銷重新整理令牌。
- 令牌佔用空間大,如果加入太多使用者憑證資訊,會存在傳輸冗餘
4).JWT Token
想使用jwt令牌,需要在授權服務中配置JwtTokenStore。之前說了,jwt將一些資訊資料編碼後存放在令牌,那麼其實在傳輸的時候是很不安全的,所以Spring OAuth2提供了JwtAccessTokenConverter來懟令牌進行編碼和解碼。適用JwtAccessTokenConverter可以自定義祕籤(SigningKey)。SigningKey用處就是在授權認證伺服器生成進行簽名編碼,在資源獲取伺服器根據SigningKey解碼校驗。
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("CatalpaFlat")
複製程式碼
1.3.端點接入-endpoints
授權認證是使用AuthorizationEndpoint這個端點來進行控制,一般使用AuthorizationServerEndpointsConfigurer 來進行配置。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}
複製程式碼
1).端點(endpoints)的相關屬性配置:
- authenticationManager:認證管理器。若我們上面的Grant Type設定為password,則需設定一個AuthenticationManager物件
- userDetailsService:若是我們實現了UserDetailsService,來管理使用者資訊,那麼得設我們的userDetailsService物件
- authorizationCodeServices:授權碼服務。若我們上面的Grant Type設定為authorization_code,那麼得設一個AuthorizationCodeServices物件
- tokenStore:這個就是我們上面說到,把我們想要是實現的Access Token型別設定
- accessTokenConverter:Access Token的編碼器。也就是JwtAccessTokenConverter
- tokenEnhancer:token的擴充。當使用jwt時候,可以實現TokenEnhancer來進行jwt對包含資訊的擴充
- tokenGranter:當預設的Grant Type已經不夠我們業務邏輯,實現TokenGranter 介面,授權將會由我們控制,並且忽略Grant Type的幾個屬性。
2).端點(endpoints)的授權url:
要授權認證,肯定得由url請求,才可以傳輸。因此OAuth2提供了配置授權端點的URL。
AuthorizationServerEndpointsConfigurer ,還是這個配置物件進行配置,其中由一個pathMapping()方法進行配置授權端點URL路徑,預設提供了兩個引數defaultPath和customPath:
public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath) {
this.patternMap.put(defaultPath, customPath);
return this;
}
複製程式碼
pathMapping的defaultPath有:
- /oauth/authorize:授權端點
- /oauth/token:令牌端點
- /oauth/confirm_access:使用者確認授權提交端點
- /oauth/error:授權服務錯誤資訊端點
- /oauth/check_token:用於資源服務訪問的令牌解析端點
- /oauth/token_key:提供公有密匙的端點,如果使用JWT令牌的話
注:pathMapping的兩個引數都將以 "/" 字元為開始的字串
1.4.自定義錯誤處理(Error Handling)
實際上我們上面說到的端點,其實可以看成Controller,用於返回不同端點的響應內容。
授權服務的錯誤資訊是使用標準的Spring MVC來進行處理的,也就是 @ExceptionHandler 註解的端點方法,我們可以提供一個 WebResponseExceptionTranslator 物件。最好的方式是改變響應的內容而不是直接進行渲染。
- 假如說在呈現令牌端點的時候發生了異常,那麼異常委託了 HttpMessageConverters 物件(它能夠被新增到MVC配置中)來進行輸出。
- 假如說在呈現授權端點的時候未通過驗證,則會被重定向到 /oauth/error 即錯誤資訊端點中。whitelabel error (即Spring框架提供的一個預設錯誤頁面)錯誤端點提供了HTML的響應,但是我們大概可能需要實現一個自定義錯誤頁面(例如只是簡單的增加一個 @Controller 對映到請求路徑上 @RequestMapping("/oauth/error"))。
2.資源獲取服務
資源伺服器,其實就是存放一些受令牌保護的資源,只有令牌並且有效正確才能獲取到資源。 內部是通過Spring OAuth2的Spring Security Authentication filter 的過濾鏈來進行保護。
2.1.ResourceServerConfigurerAdapter
我們可以繼承ResourceServerConfigurerAdapter,來使用 ResourceServerSecurityConfigurer進行相關配置。
public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
}
複製程式碼
2.2.ResourceServerSecurityConfigurer的相關屬性
- tokenServices:ResourceServerTokenServices 類的例項,用來實現令牌服務。
- resourceId:這個資源服務的ID,這個屬性是可選的,但是推薦設定並在授權服務中進行驗證。
- tokenExtractor 令牌提取器用來提取請求中的令牌。
- 請求匹配器,用來設定需要進行保護的資源路徑,預設的情況下是受保護資源服務的全部路徑。
- 受保護資源的訪問規則,預設的規則是簡單的身份驗證(plain authenticated)。
- 其他的自定義許可權保護規則通過 HttpSecurity 來進行配置。
2.3.ResourceServerTokenServices
ResourceServerTokenServices 是組成授權服務的另一半。
1).若是資源伺服器和授權服務在同一個應用,可以使用DefaultTokenServices
2).若是分離的。ResourceServerTokenServices必須知道令牌的如何解碼。
ResourceServerTokenServices解析令牌的方法:
- 使用RemoteTokenServices,資源伺服器通過HTTP請求來解碼令牌。每次都請求授權伺服器的端點-/oauth/check_toke,以此來解碼令牌
- 若是訪問量大,則通過http獲取之後,換成令牌的結果
- 若是jwt令牌,需請求授權服務的/oauth/token_key,來獲取key進行解碼
注:授權認證服務需要把/oauth/check_toke暴露出來,並且附帶上許可權訪問。
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
複製程式碼
(~ ̄▽ ̄)~未完待續... ...