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://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)
微信公眾號獲取access_token(Client Credentials Grant)。
至於Resource Owner Password Credentials Grant這種類型的許可方式,由於其適用常見,平時作為第三方開發者的開發工作中,沒有遇到此類的案例。其適用場景在於第三方應用和Resoure server屬於同一方這樣高度可信的環境下。