【轉】OAuth的改變

eedc發表於2017-01-18

原文地址:http://huoding.com/2011/11/08/126

去年我寫過一篇《OAuth那些事兒》,對OAuth做了一些簡單扼要的介紹,今天我打算寫一些細節,以闡明OAuth如何從1.0改變成1.0a,繼而改變成2.0的。

 

OAuth1.0

在OAuth誕生前,Web安全方面的標準協議只有OpenID,不過它關注的是驗證,即WHO的問題,而不是授權,即WHAT的問題。好在FlickrAuth和GoogleAuthSub等私有協議在授權方面做了不少有益的嘗試,從而為OAuth的誕生奠定了基礎。

OAuth1.0定義了三種角色:User、Service Provider、Consumer。如何理解?假設我們做了一個SNS,它有一個功能,可以讓會員把他們在Google上的聯絡人匯入到SNS上,那麼此時的會員是User,Google是Service Providere,而SNS則是Consumer。

 +----------+                                           +----------+
 |          |--(A)- Obtaining a Request Token --------->|          |
 |          |                                           |          |
 |          |<-(B)- Request Token ----------------------|          |
 |          |       (Unauthorized)                      |          |
 |          |                                           |          |
 |          |      +--------+                           |          |
 |          |>-(C)-|       -+-(C)- Directing ---------->|          |
 |          |      |       -+-(D)- User authenticates ->|          |
 |          |      |        |      +----------+         | Service  |
 | Consumer |      | User-  |      |          |         | Provider |
 |          |      | Agent -+-(D)->|   User   |         |          |
 |          |      |        |      |          |         |          |
 |          |      |        |      +----------+         |          |
 |          |<-(E)-|       -+-(E)- Request Token ------<|          |
 |          |      +--------+      (Authorized)         |          |
 |          |                                           |          |
 |          |--(F)- Obtaining a Access Token ---------->|          |
 |          |                                           |          |
 |          |<-(G)- Access Token -----------------------|          |
 +----------+                                           +----------+

花絮:OAuth1.0的RFC沒有ASCII流程圖,於是我敲了幾百下鍵盤自己畫了一個,後經網友提示,Emacs可以很輕鬆的搞定ASCII圖:Emacs Screencast: Artist Mode,VIM當然也可以搞定,不過要藉助一個外掛:DrawIt,可惜我的鍵盤都要壞了。

Consumer申請Request Token(/oauth/1.0/request_token):

oauth_consumer_key
oauth_signature_method
oauth_signature
oauth_timestamp
oauth_nonce
oauth_version

Service Provider返回Request Token:

oauth_token
oauth_token_secret

Consumer重定向User到Service Provider(/oauth/1.0/authorize):

oauth_token
oauth_callback

Service Provider在使用者授權後重定向User到Consumer:

oauth_token

Consumer申請Access Token(/oauth/1.0/access_token):

oauth_consumer_key
oauth_token
oauth_signature_method
oauth_signature
oauth_timestamp
oauth_nonce
oauth_version

Service Provider返回Access Token:

oauth_token
oauth_token_secret

注:整個操作流程中,需要注意涉及兩種Token,分別是Request Token和Access Token,其中Request Token又涉及兩種狀態,分別是未授權和已授權。

OAuth1.0a

OAuth1.0存在安全漏洞,詳細介紹:Explaining the OAuth Session Fixation Attack,還有這篇:How the OAuth Security Battle Was Won, Open Web Style

簡單點來說,這是一種會話固化攻擊,和常見的會話劫持攻擊不同的是,在會話固化攻擊中,攻擊者會初始化一個合法的會話,然後誘使使用者在這個會話上完成後續操作,從而達到攻擊的目的。反映到OAuth1.0上,攻擊者會先申請Request Token,然後誘使使用者授權這個Request Token,接著針對回撥地址的使用,又存在以下幾種攻擊手段:

  • 如果Service Provider沒有限制回撥地址(應用設定沒有限定根域名一致),那麼攻擊者可以把oauth_callback設定成成自己的URL,當User完成授權後,通過這個URL自然就能拿到User的Access Token。
  • 如果Consumer不使用回撥地址(桌面或手機程式),而是通過User手動拷貝貼上Request Token完成授權的話,那麼就存在一個競爭關係,只要攻擊者在User授權後,搶在User前面發起請求,就能拿到User的Access Token。

為了修復安全問題,OAuth1.0a出現了(RFC5849),主要修改了以下細節:

  • Consumer申請Request Token時,必須傳遞oauth_callback,而Consumer申請Access Token時,不需要傳遞oauth_callback。通過前置oauth_callback的傳遞時機,讓oauth_callback參與簽名,從而避免攻擊者假冒oauth_callback。
  • Service Provider獲得User授權後重定向User到Consumer時,返回oauth_verifier,它會被用在Consumer申請Access Token的過程中。攻擊者無法猜測它的值。

Consumer申請Request Token(/oauth/1.0a/request_token):

oauth_consumer_key
oauth_signature_method
oauth_signature
oauth_timestamp
oauth_nonce
oauth_version
oauth_callback

Service Provider返回Request Token:

oauth_token
oauth_token_secret
oauth_callback_confirmed

Consumer重定向User到Service Provider(/oauth/1.0a/authorize):

oauth_token

Service Provider在使用者授權後重定向User到Consumer:

oauth_token
oauth_verifier

Consumer申請Access Token(/oauth/1.0a/access_token):

oauth_consumer_key
oauth_token
oauth_signature_method
oauth_signature
oauth_timestamp
oauth_nonce
oauth_version
oauth_verifier

Service Provider返回Access Token:

oauth_token
oauth_token_secret

注:Service Provider返回Request Token時,附帶返回的oauth_callback_confirmed是為了說明Service Provider是否支援OAuth1.0a版本。

簽名引數中,oauth_timestamp表示客戶端發起請求的時間,如未驗證會帶來安全問題。

在探討oauth_timestamp之前,先聊聊oauth_nonce,它是用來防止重放攻擊的,Service Provider應該驗證唯一性,不過儲存所有的oauth_nonce並不現實,所以一般只儲存一段時間(比如最近一小時)內的資料。

如果不驗證oauth_timestamp,那麼一旦攻擊者攔截到某個請求後,只要等到限定時間到了,oauth_nonce再次生效後就可以把請求原樣重發,簽名自然也能通過,完全是一個合法請求,所以說Service Provider必須驗證oauth_timestamp和系統時鐘的偏差是否在可接受範圍內(比如十分鐘),如此才能徹底杜絕重放攻擊。

需要單獨說一下桌面或手機應用應該如何使用OAuth1.0a。此類應用通常沒有服務端,無法設定Web形式的oauth_callback地址,此時應該把它設定成oob(out-of-band),當使用者選擇授權後,Service Provider在頁面上顯示PIN碼(也就是oauth_verifier),並引導使用者把它貼上到應用裡完成授權。

一個問題是應用如何開啟使用者授權頁面呢?很容易想到的做法是使用內嵌瀏覽器,說它是個錯誤的做法或許有點偏激,但它至少是個對使用者不友好的做法,因為一旦瀏覽器內嵌到程式裡,那麼使用者輸入的使用者名稱密碼就有被監聽的可能;對使用者友好的做法應該是開啟新視窗,彈出系統預設的瀏覽器,讓使用者在可信賴的上下文環境中完成授權流程。

不過這樣的方式需要使用者在瀏覽器和應用間手動切換,才能完成授權流程,某種程度上說,影響了使用者體驗,好在可以通過一些其它的技巧來規避這個問題,其中一個行之有效的辦法是Monitor web-browser title-bar,簡單點說,作業系統一般提供相應的API可以讓應用監聽桌面上所有視窗的標題,應用一旦發現某個視窗標題符合預定義格式,就可以認為它是我們要的PIN碼,無需使用者參與就可以完成授權流程。Google支援這種方式,並且有資料專門描述了細節:Auto-Detecting Approval(注:牆!)。

還有一點需要注意的是對桌面或移動應用來說,consumer_key和consumer_secret通常都是直接儲存在應用裡的,所以對攻擊者而言,理論上可以通過反編譯之類的手段解出來。進而通過consumer_key和consumer_secret簽名一個偽造的請求,並且在請求中把oauth_callback設定成自己控制的URL,來騙取使用者授權。為了遮蔽此類問題,Service Provider需要強制開發者必須預定義回撥地址:如果預定義的回撥地址是URL方式的,則需要驗證請求中的回撥地址和預定義的回撥地址是否主域名一致;如果預定義的回撥地址是oob方式的,則禁止請求以URL的方式回撥。

OAuth2.0

OAuth1.0雖然在安全性上經過修補已經沒有問題了,但還存在其它的缺點,其中最主要的莫過於以下兩點:其一,簽名邏輯過於複雜,對開發者不夠友好;其二,授權流程太過單一,除了Web應用以外,對桌面、移動應用來說不夠友好。

為了彌補這些短板,OAuth2.0做了以下改變:

首先,去掉簽名,改用SSL(HTTPS)確保安全性,所有的token不再有對應的secret存在,這也直接導致OAuth2.0不相容老版本。

其次,針對不同的情況使用不同的授權流程,和老版本只有一種授權流程相比,新版本提供了四種授權流程,可依據客觀情況選擇。

在詳細說明授權流程之前,我們需要先了解一下OAuth2.0中的角色:

OAuth1.0定義了三種角色:User、Service Provider、Consumer。而OAuth2.0則定義了四種角色:Resource Owner、Resource Server、Client、Authorization Server:

  • Resource Owner:User
  • Resource Server:Service Provider
  • Client:Consumer
  • Authorization Server:Service Provider

也就是說,OAuth2.0把原本OAuth1.0裡的Service Provider角色分拆成Resource Server和Authorization Server兩個角色,在授權時互動的是Authorization Server,在請求資源時互動的是Resource Server,當然,有時候他們是合二為一的。

下面我們具體介紹一下OAuth2.0提供的四種授權流程:

Authorization Code

可用範圍:此型別可用於有服務端的應用,是最貼近老版本的方式。

 +----------+
 | resource |
 |   owner  |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier      +---------------+
 |         -+----(A)-- & Redirection URI ---->|               |
 |  User-   |                                 | Authorization |
 |  Agent  -+----(B)-- User authenticates --->|     Server    |
 |          |                                 |               |
 |         -+----(C)-- Authorization Code ---<|               |
 +-|----|---+                                 +---------------+
   |    |                                         ^      v
  (A)  (C)                                        |      |
   |    |                                         |      |
   ^    v                                         |      |
 +---------+                                      |      |
 |         |>---(D)-- Authorization Code ---------'      |
 |  Client |          & Redirection URI                  |
 |         |                                             |
 |         |<---(E)----- Access Token -------------------'
 +---------+       (w/ Optional Refresh Token)

Client向Authorization Server發出申請(/oauth/2.0/authorize):

response_type = code
client_id
redirect_uri
scope
state

Authorization Server在Resource Owner授權後給Client返回Authorization Code:

code
state

Client向Authorization Server發出申請(/oauth/2.0/token):

grant_type = authorization_code
code
client_id
client_secret
redirect_uri

Authorization Server在Resource Owner授權後給Client返回Access Token:

access_token
token_type
expires_in
refresh_token

說明:基本流程就是拿Authorization Code換Access Token。

Implicit Grant

可用範圍:此型別可用於沒有服務端的應用,比如Javascript應用。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier     +---------------+
 |         -+----(A)-- & Redirection URI --->|               |
 |  User-   |                                | Authorization |
 |  Agent  -|----(B)-- User authenticates -->|     Server    |
 |          |                                |               |
 |          |<---(C)--- Redirection URI ----<|               |
 |          |          with Access Token     +---------------+
 |          |            in Fragment
 |          |                                +---------------+
 |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
 |          |          without Fragment      |     Client    |
 |          |                                |    Resource   |
 |     (F)  |<---(E)------- Script ---------<|               |
 |          |                                +---------------+
 +-|--------+
   |    |
  (A)  (G) Access Token
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |
 +---------+

Client向Authorization Server發出申請(/oauth/2.0/authorize):

response_type = token
client_id
redirect_uri
scope
state

Authorization Server在Resource Owner授權後給Client返回Access Token:

access_token
token_type
expires_in
scope
state

說明:沒有服務端的應用,其資訊只能儲存在客戶端,如果使用Authorization Code授權方式的話,無法保證client_secret的安全。BTW:不返回Refresh Token。

Resource Owner Password Credentials

可用範圍:不管有無服務端,此型別都可用。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      v
      |    Resource Owner
     (A) Password Credentials
      |
      v
 +---------+                                  +---------------+
 |         |>--(B)---- Resource Owner ------->|               |
 |         |         Password Credentials     | Authorization |
 | Client  |                                  |     Server    |
 |         |<--(C)---- Access Token ---------<|               |
 |         |    (w/ Optional Refresh Token)   |               |
 +---------+                                  +---------------+

Clien向Authorization Server發出申請(/oauth/2.0/token):

grant_type = password
username
password
scope

AuthorizationServer給Client返回AccessToken:

access_token
token_type
expires_in
refresh_token

說明:因為涉及使用者名稱和密碼,所以此授權型別僅適用於可信賴的應用。

Client Credentials

可用範圍:不管有無服務端,此型別都可用。

 +---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

Client向Authorization Server發出申請(/oauth/2.0/token):

grant_type = client_credentials
client_id
client_secret
scope

Authorization Server給Client返回Access Token:

access_token
token_type
expires_in

說明:此授權型別僅適用於獲取與使用者無關的公共資訊。BTW:不返回Refresh Token。

流程中涉及兩種Token,分別是Access Token和Refresh Token。通常,Access Token的有效期比較短,而Refresh Token的有效期比較長,如此一來,當Access Token失效的時候,就需要用Refresh Token重新整理出有效的Access Token:

 +--------+                                         +---------------+
 |        |--(A)------- Authorization Grant ------->|               |
 |        |                                         |               |
 |        |<-(B)----------- Access Token -----------|               |
 |        |               & Refresh Token           |               |
 |        |                                         |               |
 |        |                            +----------+ |               |
 |        |--(C)---- Access Token ---->|          | |               |
 |        |                            |          | |               |
 |        |<-(D)- Protected Resource --| Resource | | Authorization |
 | Client |                            |  Server  | |     Server    |
 |        |--(E)---- Access Token ---->|          | |               |
 |        |                            |          | |               |
 |        |<-(F)- Invalid Token Error -|          | |               |
 |        |                            +----------+ |               |
 |        |                                         |               |
 |        |--(G)----------- Refresh Token --------->|               |
 |        |                                         |               |
 |        |<-(H)----------- Access Token -----------|               |
 +--------+           & Optional Refresh Token      +---------------+

Client向Authorization Server發出申請(/oauth/2.0/token):

grant_type = refresh_token
refresh_token
client_id
client_secret
scope

Authorization Server給Client返回Access Token:

access_token
expires_in
refresh_token
scope

不過並不是所有人都對OAuth2.0投贊成票,有空可以看看:OAuth 2.0對Web有害嗎?

此條目由老王發表在Technical分類目錄,並貼了OAuth標籤。將固定連結加入收藏夾。

相關文章