1 OAuth2解決什麼問題的?
舉個栗子先。小明在QQ空間積攢了多年的照片,想挑選一些照片來列印出來。然後小明在找到一家提供線上列印并且包郵的網站(我們叫它PP吧(Print Photo縮寫 😂))。
那麼現在問題來了,小明有兩個方案來得到列印的服務。
- 在自己的QQ空間把想要列印的照片下載下傳下來,然後提供給PP(直接發郵件給PP或者網盤共享給PP等等)。
- 把自己的QQ賬号密碼給PP,然後告訴PP我要列印哪些照片。
針對方案(1):小明要去下載下傳這些照片,然後給PP,小明累覺不愛,,,
針對方案(2):小明交出去自己的QQ賬号密碼,還要告訴PP哪些需要列印,哪些不需要,小明覺得自己有些小秘密不想給PP看,,,
小明覺得很痛苦,,,那麼有沒有不給PP賬号密碼,不下載下傳照片,自己選哪些要列印直接扔給PP去列印的辦法呢?OAuth走了過來扔給小明一塊肥皂...
2 OAuth2簡介
總結來說,OAuth2 是一個開放授權标準,它允許使用者(小明)讓第三方應用(PP)通路該使用者在某服務的特定私有資源(QQ空間中小明的照片,可以不包含小明的小視訊哦)但是不提供賬号密碼資訊給第三方應用(PP)。
有個小問題,為啥是OAuth2呢?1在哪?嗯,這個嘛,其實是有1和1.1版本的,隻是因為1和1.1版本流程比較複雜,應用不是很廣範,這裡就不介紹了。據筆者以前做過的項目,Twitter是使用的OAuth1.1的版本,感興趣的可以去了解下https://dev.twitter.com/oauth。
2.1 OAuth2的四個重要角色
進入正題,在OAuth2的完整授權流程中有4個重要的角色參與進來:
- Resource Owner:資源擁有者,上面栗子中的小明;
- Resource Server:資源伺服器,上面栗子中的QQ空間,它是小明想要分享照片給PP的照片的提供方;
- Client:第三方應用用戶端,上面栗子中的PP,代指任何可以消費資源伺服器的第三方應用;
- Authorization Server :授權伺服器,管理Resource Owner,Client和Resource Server的三角關系的中間層。
其中Authorization server和Resource server可以是獨立的服務提供商,也可以是在一起的,比如騰訊提供QQ空間作為資源伺服器的同時也提供授權服務。
從這裡可以看出,OAuth2在解決小明遇到的問題的過程中增加了一個Authorization server的角色。又印證了那句話,在計算機領域的所有問題都可以添加一個中間層來解決。
OAuth2解決問題的關鍵在于使用Authorization server提供一個通路憑據給Client,使得Client可以在不知道Resource owner在Resource server上的使用者名和密碼的情況下消費Resource owner的受保護資源。
3 部署OAuth2需要的完成的工作
由于OAuth2引入了Authorization server來管理Resource Owner,Client和Resource Server的三角關系,那麼想要用上OAuth2,是實作以下功能的。
- 增加一個Authorization server,提供授權的實作,一般由Resource server 來提供。
- Resource server 為第三方應用程式提供注冊接口。
- Resource server 開放相應的受保護資源的API。
- Client 注冊成為Resource server的第三方應用。
- Client 消費這些API。
作為資源服務提供商來說,1,2,3這三件事情是需要完成的。
作為第三方應用程式,要完成的工作是在4和5這兩個步驟中。
其中作為Resource owner來說,是不用做什麼的,是OAuth2受益的千千萬萬的最終人類使用者。
3.1 作為Resource server
在一般情況下,Resource server提供Authorization server服務,主要提供兩類接口:
- 授權接口:接受Client的授權請求,引導使用者到Resource server完成登陸授權的過程。
- 擷取通路令牌接口:使用授權接口提供的許可憑據來頒發Resource owner的通路令牌給Client,或者由Client更新過期的通路令牌。
除此之外,還需要提供一個第三方應用程式注冊管理的服務。通常情況下會為注冊完成的第三方應用程式配置設定兩個成對出現的重要參數:
- client_id:第三方應用程式的一個辨別id,這個資訊通常是公開的資訊,用來區分哪一個第三方應用程式。
- client_secret:第三方應用程式的私鑰資訊,這個資訊是私密的資訊,不允許在OAuth2流程中傳遞的,用于安全方面的檢測和加密。
3.2 作為Client
在Client取得client_id和client_secret之後。使用這些資訊來發起授權請求、擷取access_token請求和消費受保護的資源。
4 OAuth2的授權流程
貼個圖瞅瞅OAuth2的工作流程:
在上述的OAuth完整流程中,(A)->(B)->(C)->(D)是授權的過程(參與者有小明,PP,QQ空間,Authorization server);(E)->(F)是消費資源的過程(參與者有PP和QQ空間)。
- (A)小明通路PP,PP向QQ空間發起授權請求;
- (B)QQ空間接受PP的授權請求,并傳回授權許可給PP;
- (C)PP使用授權許可向Authorization server發起請求;
- (D)Authorization server驗證PP的身份和授權許可,發送通路令牌給PP;
- (E)PP用通路令牌請求小明存儲在QQ空間的照片;
- (F)QQ空間根據通路令牌,傳回小明的照片資訊給PP。
這其中比較重要的一個概念是通路令牌 ,它代表的資訊是整個OAuth2的核心,也是ABCD這些步驟最終要得到的資訊。
通路令牌是對PP可以在QQ空間通路小明的哪些資訊這個完整權限的一個抽象,比如PP要通路小李在QQ空間的照片,那麼就是另外一個通路令牌了。
通路令牌背後抽象的資訊有哪些呢?如下3類資訊。
- 用戶端辨別(比如PP);
- 使用者辨別(比如小明);
- 用戶端能通路資源所有者的哪些資源以及其相應的權限。
有了這三類資訊,那麼資源伺服器(Resouce Server)就可以區分出來是哪個第三方應用(Client)要通路哪個使用者(Resource Owner)的哪些資源(以及有沒有權限)。
5 OAuth2的4種授權許可
上一小節介紹了OAuth2的授權流程,除了通路令牌之外,還有一個重要的概念授權許可(Authorization Grant)。
書面化的方式解釋就是授權許可是一個代表資源所有者授權(通路受保護資源)的憑據,用戶端用它來擷取通路令牌。讀起來比較抽象,翻一下就是授權許可是小明授予PP獲得QQ空間通路令牌的一個憑據。
那麼如何獲得這個憑據呐,OAuth2定義了四種許可類型以及用于定義其他類型的可擴充性機制:
- Authorization Code:授權碼;
- Implicit:隐式許可;
- Resource Owner Password Credentials:資源所有者密碼憑據;
- Client Credentials :用戶端憑據。
注意:以下4種授權許可是對上述(4. OAuth2的授權流程)中的ABDE四個階段的展開。
5.1 Authorization Code
這是OAuth2最常用的一種授權許可類型,比如QQ,微網誌,Facebook和豆瓣等等。要求Client具有可公開通路的Server伺服器來接受Authorization Code,具體的流程如下:
上圖ABCDE這5個步驟,既是完整的擷取通路令牌的一個過程,其中引入了一些其他的概念,比如用戶端辨別,重新整理令牌等和重定向URL等概念,後續會在6. OAuth2附屬概念和流程介紹。
- (A)Client使用浏覽器(使用者代理)通路Authorization server。也就是用浏覽器通路一個URL,這個URL是Authorization server提供的,通路的收Client需要提供(用戶端辨別,請求範圍,本地狀态和重定向URL)這些參數。
- (B)Authorization server驗證Client在(A)中傳遞的參數資訊,如果無誤則提供一個頁面供Resource owner登陸,登陸成功後選擇Client可以通路Resource server的哪些資源以及讀寫權限。
- (C)在(B)無誤後傳回一個授權碼(Authorization Code)給Client。
- (D)Client拿着(C)中獲得的授權碼(Authorization Code)和(用戶端辨別、重定向URL等資訊)作為參數,請求Authorization server提供的擷取通路令牌的URL。
- (E)Authorization server傳回通路令牌和可選的重新整理令牌以及令牌有效時間等資訊給Client。
5.1.1 Authorization Request
對應步驟(A),用戶端提供以下參數請求Authorization Server:
- response_type:必選。值固定為“code”。
- client_id:必選。第三方應用的辨別ID。
- state:推薦。Client提供的一個字元串,伺服器會原樣傳回給Client。
- redirect_uri:必選。授權成功後的重定向位址。
- scope:可選。表示授權範圍。
比如以下示例:
GET /authorize?response_type=code&client_id=1&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2&scope=user,photo HTTP/1.1
Host: server.example.com
5.1.2 Authorization Response
對應步驟(C),Authorization Server會傳回如下資訊:
- code:授權碼。
- state:步驟(A)中用戶端提供的state參數原樣傳回。
比如示例如下:
HTTP/1.1 302 Found
Location: https://client.example.com/oauth2?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
Location頭部資訊指向步驟(A)提供的redirect_uri位址,同時攜帶code資訊和state資訊給client,這樣浏覽器在重定向的時候就會已GET的方式通路Client提供的redirect_uri,同時Client接收到code資訊和state資訊。下一步就可以請求access_token了。
5.1.3 Access Token Request
對應步驟(D),用戶端提供以下參數請求Authorization Server:
- grant_type:必選。固定值“authorization_code”。
- code : 必選。Authorization Response 中響應的code。
- redirect_uri:必選。必須和Authorization Request中提供的redirect_uri相同。
- client_id:必選。必須和Authorization Request中提供的client_id相同。
比如以下示例:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=123&client_id=1&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2
5.1.4 Access Token Response
對應步驟(E),Authorization Server會傳回如下典型的資訊:
- access_token:通路令牌。
- refresh_token:重新整理令牌。
- expires_in:過期時間。
比如以下示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
“access_token”:“2YotnFZFEjr1zCsicMWpAA”,
“token_type”:“example”,
“expires_in”:3600,
“refresh_token”:“tGzv3JOkF0XG5Qx2TlKWIA”,
“example_parameter”:“example_value”
}
5.2 Implicit
這個是Authorization Code的簡化版本。其中省略掉了頒發授權碼(Authorization Code)給用戶端的過程,而是直接傳回通路令牌和可選的重新整理令牌。其适用于沒有Server伺服器來接受處理Authorization Code的第三方應用,其流程如下:
其步驟就不做詳細介紹了,相信大家都能了解。和Authorzation Code類型下重要的區分就是省略了Authorization Response和Access Token Request。而是直接由Authorization Request傳回Access Token Response資訊,具體如下。
5.2.1 Authorization Request
用戶端提供以下參數請求Authorization Server:
- response_type:必選。值固定為“token”。
- client_id:必選。第三方應用的辨別ID。
- state:推薦。Client提供的一個字元串,伺服器會原樣傳回給Client。
- redirect_uri:可選。授權成功後的重定向位址。
- scope:可選。表示授權範圍。
重點差別在于response_type為“token”,而不再是“code”,redirect_uri也變為了可選參數。
比如以下示例:
GET /authorize?response_type=token&client_id=1&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2&scope=user,photo HTTP/1.1
Host: server.example.com
5.2.2 Access Token Response
Authorization Server會傳回如下典型的資訊:
- access_token:通路令牌。
- refresh_token:重新整理令牌。
- expires_in:過期時間。
比如以下示:
HTTP/1.1 302 Found
Location: http://client.example.com/oauth2#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&expires_in=3600
注意其和Authorization Code的最大差別在于它是把token資訊放在了url的hash部分(#後面),而不是作為參數(?後面)。這樣浏覽器在通路重定向的Location指定的url時,就不會把這些資料發送到伺服器。而Client可以通過讀取Location頭資訊中擷取到access_token資訊。
5.3 Resource Owner Password Credentials Grant
看看流程圖:
這種模式再一步簡化,和Authorzation Code類型下重要的區分就是省略了Authorization Request和Authorization Response。而是Client直接使用Resource owner提供的username和password來直接請求access_token(直接發起Access Token Request然後傳回Access Token Response資訊)。這種模式一般适用于Resource server高度信任第三方Client的情況下。
用戶端提供以下參數請求Authorization Server:
- grant_type:必選。值固定為“password”。
- username:必選。使用者登陸名。
- passward:必選。使用者登陸密碼。
- scope:可選。表示授權範圍。
比如以下示例:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=blackheart&password=1234
Access Token Response和Authorization Code一緻,就不列出來了。
5.4 Client Credentials Grant
這種類型就更簡化了,Client直接已自己的名義而不是Resource owner的名義去要求通路Resource server的一些受保護資源。
用戶端提供以下參數請求Authorization Server:
- grant_type:必選。值固定為“client_credentials”。
- scope:可選。表示授權範圍。
比如以下示例:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
Access Token Response和Authorization Code一緻,就不列出來了。
以筆者以前做公共号開發的經驗,它提供由這類的OAuth2許可類型,這個場景下得到的access_token的所屬人是公衆号的,可以用此access_token來擷取所有已關注的使用者的資訊,而不局限于特定的某一個使用者,正是Client Credentials Grant這種類型的許可的用武之地,案例文檔位址在文章最後面。
6 OAuth2重新整理令牌
在上述得到通路令牌(access_token)時,一般會提供一個過期時間和重新整理令牌。以便在通路令牌過期失效的時候可以由用戶端自動擷取新的通路令牌,而不是讓使用者再次登陸授權。那麼問題來了,是否可以把過期時間設定的無限大呢,答案是可以的,筆者記得Pocket的OAuth2拿到的通路令牌就是無限期的,好像豆瓣的也是。如下是重新整理令牌的收用戶端需要提供給Authorization Server的參數:
- grant_type:必選。固定值“refresh_token”。
- refresh_token:必選。用戶端得到access_token的同時拿到的重新整理令牌。
比如以下示例:
POST /token HTTP/1.1
Host: server.example.com
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
響應資訊和5.1.4 Access Token Response保持一緻。
7 Token傳遞方式
在第三方Client拿到access_token後,如何發送給Resouce Server這個問題并沒有在RFC6729種定義,而是作為一個單獨的RFC6750來獨立定義了。這裡做以下簡單的介紹,主要有三種方式如下:
- URI Query Parameter.
- Authorization Request Header Field.
- Form-Encoded Body Parameter.
7.1 URI Query Parameter
這種使用途徑應該是最常見的一種方式,非常簡單,比如:
GET /resource?access_token=mF_9.B5f-4.1JqM HTTP/1.1
Host: server.example.com
在我們請求受保護的資源的Url後面追加一個access_token的參數即可。另外還有一點要求,就是Client需要設定以下Request Header的Cache-Control:no-store,用來阻止access_token不會被Web中間件給log下來,屬于安全防護方面的一個考慮。
7.2 Authorization Request Header Field
因為在HTTP應用層協定中,專門有定義一個授權使用的Request Header,是以也可以使用這種方式:
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
其中"Bearer "是固定的在access_token前面的頭部資訊。
7.3 Form-Encoded Body Parameter
使用Request Body這種方式,有一個額外的要求,就是Request Header的"Content-Type"必須是固定的“application/x-www-form-urlencoded”,此外還有一個限制就是不可以使用GET通路,這個好了解,畢竟GET請求是不能攜帶Request Body的。
POST /resource HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
access_token=mF_9.B5f-4.1JqM
8 OAuth2的安全問題
在OAuth2早期的時候爆發過不少相關的安全方面的漏洞,其實仔細分析後會發現大都都是沒有嚴格遵循OAuth2的安全相關的指導造成的,相關的漏洞事件百度以下就有了。
其實OAuth2在設計之初是已經做了很多安全方面的考慮,并且在RFC6749中加入了一些安全方面的規範指導。比如
- 要求Authorization server進行有效的Client驗證;
- client_serect,access_token,refresh_token,code等敏感資訊的安全存儲(不得洩露給第三方)、傳輸通道的安全性(TSL的要求);
- 維持refresh_token和第三方應用的綁定,重新整理失效機制;
- 維持Authorization Code和第三方應用的綁定,這也是state參數為什麼是推薦的一點,以防止CSRF;
- 保證上述各種令牌資訊的不可猜測行,以防止被猜測得到;
安全無小事,這方面是要靠各方面(開放平台,第三方開發者)共同防範的。如QQ互聯的OAuth2 API中,state參數是強制必選的參數,授權接口是基于HTTPS的加密通道等;同時作為第三方開發者在使用消費這些服務的時候也應該遵循其相關的安全規範。
9 總結 & 參考 & 案例
OAuth2是一種授權标準架構,用來解決的是第三方服務在無需使用者提供賬号密碼的情況下通路使用者的私有資源的一套流程規範。與其配套的還有其他相關的規範,都可以到https://oauth.net/2/去延伸閱讀和了解。
相關參考:
https://oauth.net/2/
https://www.oauth.com/
https://aaronparecki.com/oauth-2-simplified/
RFC6749 : The OAuth 2.0 Authorization Framework
RFC6749中文版(https://github.com/jeansfish/RFC6749.zh-cn)
RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage.
RFC6819 - OAuth 2.0 Threat Model and Security Considerations.
OAuth2案例:
豆瓣OAuth2 API(Authorization Code)
QQ OAuth2 API(Authorization Code)
豆瓣OAuth2 API(Implicit )
QQ OAuth2 API(Implicit)
微信公衆号擷取access_token(Client Credentials Grant)。
至于Resource Owner Password Credentials Grant這種類型的許可方式,由于其适用常見,平時作為第三方開發者的開發工作中,沒有遇到此類的案例。其适用場景在于第三方應用和Resoure server屬于同一方這樣高度可信的環境下。