Spring Security 實戰乾貨:OAuth2授權請求是如何構建並執行的

碼農小胖哥發表於2020-11-11

Spring Security 實戰乾貨:客戶端OAuth2授權請求的入口中我們找到了攔截OAuth2授權請求入口/oauth2/authorization的過濾器OAuth2AuthorizationRequestRedirectFilter,並找到了真正發起OAuth2授權請求的方法sendRedirectForAuthorization。但是這個方法並沒有細說,所以今天接著上一篇把這個坑給補上。

2. sendRedirectForAuthorization

這個sendRedirectForAuthorization方法沒多少程式碼,它的主要作用就是向第三方平臺進行授權重定向訪問。它所有的邏輯都和OAuth2AuthorizationRequest有關,因此我們對OAuth2AuthorizationRequest進行輕描淡寫是不行的,我們必須掌握OAuth2AuthorizationRequest是怎麼來的,幹嘛用的。

OAuth2AuthorizationRequestResolver

這就需要去分析解析類OAuth2AuthorizationRequestResolver,其核心方法有兩個過載,這裡分析一個就夠了。

@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
    // registrationId是通過uri路徑引數/oauth2/authorization/{registrationId}獲得的
   String registrationId = this.resolveRegistrationId(request);
    // 然後去請求物件request中提取key為action的引數,預設值是login
   String redirectUriAction = getAction(request, "login");
    // 然後進入根本的解析方法
   return resolve(request, registrationId, redirectUriAction);
}

上面方法裡面的resolve(request, registrationId, redirectUriAction)方法才是最終從/oauth2/authorization提取OAuth2AuthorizationRequest的根本方法。程式碼太多但是我儘量通俗易懂的來進行圖解。resolve方法會根據不同的授權方式(AuthorizationGrantType)來組裝不同的OAuth2AuthorizationRequest

3. OAuth2AuthorizationRequest

接下來就是OAuth2.0協議的核心重中之重了,可能以後你定製化的參考就來自這裡,這是圈起來要考的知識點。我會對OAuth2AuthorizationRequestResolver在各種授權方式下的OAuth2AuthorizationRequest物件的解析進行一個完全的總結歸納。大致分為以下兩部分:

3.1 由AuthorizationGrantType決定的

在不同AuthorizationGrantType下對OAuth2AuthorizationRequest的梳理。涉及到的成員變數有:

  • authorizationGrantType ,來自配置spring.security.client.registration.{registrationId}.authorizationGrantType
  • responseType , 由authorizationGrantType 的值決定,參考下面的JSON。
  • additionalParameters,當authorizationGrantType值為authorization_code時需要額外的一些引數,參考下面JSON 。
  • attributes,不同的authorizationGrantType存在不同的屬性。

其中類似{registrationId} 的形式表示 {registrationId}是一個變數,例如 registrationId=gitee

在OAuth2客戶端配置spring.security.client.registration.{registrationId}的字首中有以下五種情況。

scope 不包含openid而且client-authentication-method不為none時上述四個引數:

{
  "authorizationGrantType": "authorization_code",
  "responseType": "code",
  "additionalParameters": {},
  "attributes": {
    "registration_id": "{registrationId}"
  }
}

scope 包含openid而且client-authentication-method不為none時上述四個引數:

{
  "authorizationGrantType": "authorization_code",
  "responseType": "code",
  "additionalParameters": {
    "nonce": "{nonce}的Hash值"
  },
  "attributes": {
    "registration_id": "{registrationId}",
    "nonce": "{nonce}"
  }
}

scope不包含openid而且client-authentication-methodnone時上述四個引數:

{
  "authorizationGrantType": "authorization_code",
  "responseType": "code",
  "additionalParameters": {
    "code_challenge": "{codeVerifier}的Hash值",
    // code_challenge_method 當不是SHA256可能沒有該key
    "code_challenge_method": "S256(如果是SHA256演算法的話)"
  },
  "attributes": {
    "registration_id": "{registrationId}",
    "code_verifier": "Base64生成的安全{codeVerifier}"
  }
}

scope包含openid而且client-authentication-methodnone時上述四個引數:

{
  "authorizationGrantType": "authorization_code",
  "responseType": "code",
  "additionalParameters": {
    "code_challenge": "{codeVerifier}的Hash值",
    // code_challenge_method 當不是SHA256可能沒有該key
    "code_challenge_method": "S256(如果是SHA256演算法的話)",
    "nonce": "{nonce}的Hash值"
  },
  "attributes": {
    "registration_id": "{registrationId}",
    "code_verifier": "Base64生成的安全{codeVerifier}",
    "nonce": "{nonce}"
  }
}

implicit下要簡單的多:

{
  "authorizationGrantType": "implicit",
  "responseType": "token",
  "attributes": {}
}

3.2 固定規則部分

上面是各種不同AuthorizationGrantType下的OAuth2AuthorizationRequest的成員變數個性化取值策略, 還有幾個引數的規則是固定的:

  • clientId 來自於配置,是第三方平臺給予我們的唯一標識。
  • authorizationUri來自於配置,用來構造向第三方發起的請求URL。
  • scopes 來自於配置,是第三方平臺給我們授權劃定的作用域,可以理解為角色。
  • state 自動生成的,為了防止csrf 攻擊。
  • authorizationRequestUri 向第三方平臺發起授權請求的,可以直接通過OAuth2AuthorizationRequest的構建類來設定或者通過上面的authorizationUri等引數來生成,稍後會把構造機制分析一波。
  • redirectUriOAuth2AuthorizationRequest被第三方平臺收到後,第三方平臺會回撥這個URI來對授權請求進行相應,稍後也會來分析其機制。

authorizationRequestUri的構建機制

如果不顯式提供authorizationRequestUri就會通過OAuth2AuthorizationRequest中的

  • responseType
  • clientId
  • scopes
  • state
  • redirectUri
  • additionalParameters

按照下面的規則進行拼接成authorizationUri的引數串,引數串的keyvalue都要進行URI編碼。

authorizationUri?response_type={responseType.getValue()}&client_id={clientId}&scope={scopes元素一個字元間隔}&state={state}&redirect_uri={redirectUri}&{additionalParameter展開進行同樣規則的KV引數串}

然後OAuth2AuthorizationRequestRedirectFilter負責重定向到authorizationRequestUri向第三方請求授權。

redirectUri

第三方收到響應會呼叫redirectUri,回撥也是有一定預設規則的,它遵循{baseUrl}/{action}/oauth2/code/{registrationId}的路徑引數規則。

  • baseUrl 是從我們/oauth2/authorization請求中提取的基礎請求路徑。
  • action,有兩種預設值loginauthorize ,當/oauth2/authorization請求中包含了action引數時會根據action的值進行填充。
  • registrationId 這個就不用多說了。

4. 總結

通過對OAuth2AuthorizationRequest請求物件的規則進行詳細分析,我們應該能大致的知道的過濾器OAuth2AuthorizationRequestRedirectFilter流程:

  1. 通過客戶端配置構建ClientRegistration,後續可以進行持久化。
  2. 攔截/oauth2/authorization請求並構造OAuth2AuthorizationRequest,然後重定向到authorizationRequestUri進行請求授權。
  3. 第三方通過redirect_uri進行相應。

那麼Spring Security OAuth2如何對第三方的回撥相應進行處理呢?關注:碼農小胖哥 為你揭曉這個答案。

關注公眾號:Felordcn 獲取更多資訊

個人部落格:https://felord.cn

相關文章