【轉】OAuth的改變

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

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

 

OAuth1.0

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

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

 +----------+                                           +----------+
 |          |--(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,惋惜個人鍵盤都要壞了。api

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):session

oauth_token
oauth_callback

Service Provider在用戶受權後重定向User到Consumer:app

oauth_token

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

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標籤。將固定連接加入收藏夾。

相關文章
相關標籤/搜索