Spring security OAuth2 深刻解析

Spring security OAuth2 深刻解析

1、OAuth2 概要

1.1.OAuth2基本流程

話很少說,先上圖: spring

OAuth2流程圖

分析一波:數據庫

  • client:第三方應用(即App或向外提供接口)
  • Resource Owner:資源全部者(即用戶)
  • Authentication Server:受權認證服務(發配Access Token)
  • Resource Server:資源服務器(存儲用戶資源信息等資源)

其實無論微信或者QQ大致上都是使用這種OAuth2的基本流程:api

  1. 第三方應用請求用戶受權;
  2. 用戶贊成受權,並返回一個受權碼(code);
  3. 第三方應用根據受權碼(code)向受權認證服務進行受權;
  4. 受權服務器根據受權碼(code),校驗經過,並返回給第三方應用令牌(Access Token);
  5. 第三方應用根據令牌(Access Token)向資源服務請求相關資源;
  6. 資源服務器驗證令牌(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.受權認證服務

主要的操做:微信

  1. 獲取第三方應用發送的受權碼(code)以及第三方應用標識
  2. 根據受權碼及標識進行校驗
  3. 校驗經過,發送令牌(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)的安全性。app

2)第二步操做框架

  • 受權認證服務器根據標識校驗第三方應用的真實性
  • 受權認證服務器根據受權碼(code)進行校驗用戶憑證

3)第三步操做

  • 生成Access Token(MD5類型,uuid類型,jwt類型等)

1.4.資源獲取服務

主要的操做:

  • 校驗Access Token
  • 發放資源信息

2、Spring Security OAuth2的使用

1.受權認證服務

spring OAuth2中,咱們配置一個受權認證服務,咱們最主要有如下三點:

  1. 第三方用戶客戶端詳情 → Client
  2. 令牌的生成管理 → Access Token
  3. 端點接入 → 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")
複製代碼

jwt存儲

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')");
}
複製代碼

(~ ̄▽ ̄)~未完待續... ...

相關文章
相關標籤/搜索