OAuth2 快速入門
1 OAuth簡述
OAuth 2.0 是一個授權協議,它允許軟件應用代表(而不是充當)資源擁有者去訪問資源擁有者的資源。應用向資源擁有者請求授權,然後取得令牌(token),並用它來訪問資源,並且資源擁有者不用嚮應用提供用戶名和密碼等敏感數據。
2 OAuth角色
OAuth整個授權過程中定義了4種角色:
- 客戶端(Cilent):代表資源擁有者訪問受保護資源的軟件,它使用OAuth 來獲取訪問權限;
- 資源擁有者(Resource Owner):是有權將訪問權限授權給客戶端的主體,在大多數情況下,資源擁有者是一個人,他使用客戶端軟件訪問受他控制的資源;
- 資源服務器(Resource Server):資源服務器能夠通過HTTP 服務器進行訪問,在訪問時需要OAuth 訪問令牌。受保護資源需要驗證收到的令牌,並決定是否響應以及如何響應請求;
- 授權服務器(Authorization Server):一個HTTP 服務器,它在OAuth 系統中充當中央組件。授權服務器對資源擁有者和客戶端進行身份認證,讓資源擁有者向客戶端授權、為客戶端頒發令牌。某些授權服務器還會提供額外的功能,例如令牌內省、記憶授權決策;
假設你使用了一個照片雲存儲服務和一個雲打印服務,並且想使用雲打印服務來打印存放在雲存儲服務上的照片。很幸運,這兩個服務能夠使用API 來通信。這很好,但兩個服務由不同的公司提供,這意味着你在雲存儲服務上的賬戶和在雲打印服務上的賬戶沒有關聯。使用OAuth 可以解決這個問題:授權雲打印服務訪問照片,但並不需要將存儲服務上的賬戶密碼交給它。
在這上面這一段中:
客戶端 : 雲打印服務
資源擁有者:你
資源服務器,授權服務器:照片雲存儲服務
3 OAuth授權許可類型
3.1 授權碼許可類型(Grant Type: Authorization Code)
資源擁有者通過在授權服務器完成登錄,獲取授權碼的形式,客戶端通過Ajax請求最終兌換令牌,該種形式使得令牌對外是不可見的,一定程度上保證了令牌的安全,通常用於Web端的OAuth2.0 開發。
具體流程步驟為:
(1)資源擁有者在授權服務器完成登錄,並完成授權,資源服務器重定向至客戶端,並附帶一個臨時授權碼,該授權碼是短時效性的;
(2)客戶端獲取到臨時授權碼後,通過臨時授權碼以及其他應用信息,向資源服務器接口請求,以此來換取令牌(後端接口請求)。
3.2 隱式許可類型(Grant Type:Implicit)
有些 Web 應用是純前端應用,沒有後端。此時就沒有使用授權碼形式的必要了,而是直接返回令牌,省略了授權碼再去兌換令牌的步驟(該步驟本是在後端進行,目的是對前端隱藏令牌內容)。隱式許可流程不可用於獲取刷新令牌。因為瀏覽器內的應用具有短暫運行的特點,只會在被加載到瀏覽器的期間保持會話。
// 請求,response_type 參數的值為token,其為一個授權頁面,用戶需要在上面完成授權,授權完成後,會重定向至redirect_uri
HTTP/1.1 302 Moved Temporarily
Location: //localhost:9001/authorize?response_type=token&scope=foo&client_
id=oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state
=Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
Vary: Accept
Content-Type: text/html; charset=utf-8
Content-Length: 444
Date: Fri, 31 Jul 2015 20:50:19 GMT
// 返回,直接返回accss_token,注意此處為 #access_token
GET /callback#access_token=987tghjkiu6trfghjuytrghj&token_type=Bearer
HTTP/1.1
Host: localhost:9000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0)
Gecko/20100101 Firefox/39.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: //localhost:9001/authorize?response_type=code&scope=foo&client_id=
oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state=
Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
3.3 客戶端憑證許可類型(Grant Type:Client Credential)
後端系統之間需要直接通信,且本身並不代表某個特定用戶,沒有用戶對客戶端授權。客戶端直接向授權服務器進行身份認證,而授權服務器給客戶端頒發訪問令牌。需要提供客戶端id以及key(在對接前,手動申請)
其大致的請求如下:
POST /token
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
grant_type=client_credentials&scope=foo%20bar&client_id=abcdsafdf&client_secret=qdfjadfj
3.4 斷言許可類型
在斷言許可類型下,客戶端會得到一條結構化的且被加密保護的信息,叫作斷言,使用斷言向授權服務器換取令牌。這種許可類型只使用後端信道,與客戶端憑據許可類型很相似,沒有明確的資源擁有者參與。與客戶端憑據流程不同的是,由此頒發的令牌所關聯的權限取決於所出示的斷言,而不僅僅取決於客戶端本身。由於斷言一般來自於客戶端之外的第三方,因此客戶端可以不知道斷言本身的含義。
目前標準的斷言格式:
- 安全斷言標記語言:SAML
- JSON WEB TOKEN: JWT
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYS0xIn0.eyJpc3MiOi
JodHRwOi8vdHJ1c3QuZXhhbXBsZS5uZXQvIiwic3ViIjoib2F1dGgtY2xpZW50LTEiLCJzY29wZSI....
3.5 資源擁有者許可憑證(不推薦使用)
如果資源擁有者在授權服務器上有純文本的用戶名和密碼,那麼客戶端可以向用戶索取用戶的憑據,然後用這個憑據換取令牌,也叫作密碼流程。
資源擁有者與之直接交互的是客戶端,而不是授權服務器。這種許可類型只使用令牌端點,並且只通過後端信道通信。
3.6 授權許可類型如何選擇
4 OAuth 安全相關
4.1 CSRF攻擊
授權碼許可和隱式許可類型中都可以使用的state 參數,這個參數是一個隨機數,作為接口請求參數。客戶端使用state參數來維持請求與回調之間狀態的不透明值。授權服務器在將用戶代理重定向回客戶端時包含該值。應該使用這個參數,它可以防止CSRF(cross-site request forgery,跨站請求偽造)。後續有實際例子。
4.2 授權服務器安全
- 授權碼使用一次之後將其銷毀。
- 授權服務器應該採用精確匹配的重定向URI 校驗算法,這是唯一安全的方法。
- 完全按照OAuth 核心規範來實現授權服務器可能會導致它成為一個開放重定向器。如果這個重定向器能受到妥善的監控,則情況還好,但稍有不慎則會面臨風險。
- 留意在進行錯誤提示的過程中,信息有可能通過URI 片段或者Referrer 頭部遭泄露。
4.3 授權碼安全
授權碼可以避免將令牌直接暴露給其他人,但是授權碼仍可能白劫持。授權碼本身是沒有用的,特別是客戶端擁有用於自身身份認證的密鑰的情況下。然而,原生應用在客戶端密鑰方面存在特殊問題(APP可能需要把密鑰寫死在源代碼中導致泄露);
解決辦法:PKCE(Proof Key for Code Exchange)
(1)客戶端創建並記錄名為code_verifier的秘密信息
(2)客戶端根據code_verifier計算code_challenge(例如:密碼散列MAC)
(3)客戶端請求授權服務器,並附帶code_challenge以及code_challenge_method(計算方法,可選);
(4)授權服務器正常響應,並記錄code_challenge和code_challenge_method(與授權碼關聯);
(5)客戶端接收到授權碼後,攜帶之前生成的code_verifier,執行令牌申請請求;
(6)授權服務器計算code_challenge,檢測是否一致。
5 OAuth 令牌
OAuth 系統中的客戶端無須了解令牌本身的任何信息。客戶端需要知道的就是如何從授權服務器獲取令牌以及如何在資源服務器上使用令牌。但是,授權服務器和資源服務器需要了解令牌的內容。授權服務器要知道如何生成令牌來頒發給客戶端,資源服務器要知道如何識別並驗證客戶端發送過來的令牌。
- OAuth 令牌可以具有有效期,可以支持撤回,也可以永久有效,或者根據情況將這些特性組合;
- 令牌可以代表特定的用戶或者系統中所有的用戶,也可以不代表任何用戶;
- 令牌可以具有內部結構,可以是隨機的無意義字符串,也可以被加密保護,甚至可以將這幾項結合起來。
- 授權服務器生成令牌之後,會將令牌值存儲在磁盤上的共享數據庫中(非必須)。當受保護資源從客戶端收到令牌之後,它會在同一個數據庫中查找令牌值,以確定令牌有效。這種令牌不攜帶任何信息,只是充當數據庫查詢的檢索值。
5.1 結構化令牌:JWT
可參考內容:JSON Web Tokens – jwt.io
- 優勢:不向共享數據庫查詢,將所有必要的信息放在令牌內部,授權服務器可以通過令牌本身間接地與受保護資源溝通,而不需要調用任何網絡API。
- 劣勢:頒發的令牌無法撤回。
5.1.1 JWT結構
JWT 的核心是將一個JSON 對象封裝為一種用於網絡傳輸的格式。整體機構通過句點分割,每個部分是由Base64URL編碼的JSON對象,通過三個部分組成:
- 頭部(head):聲明簽名算法,以及負載類型
- 負載(payload):用戶數據
- 簽名(signature):對前兩部分的簽名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
5.1.2 payload常用申明
5.1.3 JWT簽名:JOSE
這裡分享兩種算法:
(1)HAMC算法:使用對稱密鑰,授權服務器和資源服務器都能夠生成令牌,因為它們都擁有創建令牌所需的密鑰。
(2)RSA算法:使用公鑰加密的話,授權服務器擁有公鑰和私鑰,可用於生成令牌,而受保護資源則只能訪問授權服務器的公鑰,用於驗證令牌。與HMAC相比,資源服務器就無法自己生成有效的令牌。
令牌請求資源服務器時,再對其進行驗簽,驗簽成功再解析payload部分。只有簽名有效才能繼續解析JWT 並檢查其內容的一致性。如果所有檢查都通過,就可以將它交給應用使用。
5.2 在線獲取令牌信息:令牌內省
將令牌信息打包放入令牌本身也有其不足之處。為了包含所有必要的聲明以及保護這些聲明所需的密碼結構,令牌尺寸會變得非常大。而且,如果受保護資源完全依賴令牌本身所包含的信息,則一旦將有效的令牌生成並發佈,想要撤回會非常困難。
令牌內省:授權服務器向客戶端頒發令牌,客戶端向受保護資源出示令牌,受保護資源則向授權服務器查詢令牌狀態(內省請求:資源服務器發送給授權服務器內省端點的表單形式的HTTP 請求,詢問該令牌是否有效)
受保護資源在請求過程中需要向授權服務器進行身份認證(提供client_id以及client_secret),以便授權服務器知道是誰在詢問,並可能根據詢問者的身份返回不同的響應。內省協議只是要求受保護資源進行身份認證,並未規定如何認證。
POST /introspect HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic cHJvdGVjdGVkLXJlc291cmNlLTE6cHJvdGVjdGVkLXJlc291cmNlLXNlY3JldC0x
token=987tghjkiu6trfghjuytrghj
// 內省請求的響應是一個JSON 對象,用於描述令牌信息。它的內容與JWT 的載荷相似
HTTP 200 OK
Content-type: application/json
{
"active": true,
"scope": "foo bar baz",
"client_id": "oauth-client-1",
"username": "alice",
"iss": "//localhost:9001/",
"sub": "alice",
"aud": "//localhost:/9002/",
"iat": 1440538696,
"exp": 1440538996,
}
內省協議規範還在JWT 的基礎上增加了幾個聲明定義,其中最重要的是active 聲明。此聲明告訴受保護資源當前令牌在授權服務器上是否有效,且是唯一必須返回的聲明。
使用令牌內省會導致OAuth 系統內的網絡流量增加。為了解決這個問題,允許受保護資源緩存給定令牌的內省請求結果。建議設置短於令牌生命周期的緩存有效期,以便降低令牌被撤回 但緩存還有效的可能性。
5.3 令牌生命周期管理
OAuth 令牌通常遵循一個可預測的生命周期。令牌由授權服務器創建,由客戶端使用,並由受保護資源驗證。它們可能會自行失效,也可能被資源擁有者(或者管理員)從授權服務器上撤回。對於JWT等格式的令牌無效,只能過期但無法失效。
5.3.1 令牌撤回協議
OAuth 令牌撤回是一個簡單的協議,它讓客戶端可以很簡潔地告訴授權服務器將本來有效的令牌撤回。客戶端需要向一個專門的撤回端點發送附帶身份認證的HTTP POST 請求,並將要撤回的令牌作為表單參數放入請求主體。
POST /revoke HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
token=987tghjkiu6trfghjuytrghj
客戶端身份認證時使用的憑據與在令牌端點上使用的憑據相同(判斷是否時該客戶端申請的令牌)。授權服務器會查詢令牌值,如果找到令牌,會將它從存儲令牌的地方刪除,並返迴響應告知客戶端刪除成功。如果授權服務器未找到令牌,或者不允許出示令牌的客戶端撤回該令牌,授權服務器還是會返回操作成功,目的是不向客戶端透露本不屬於它的令牌信息。
5.3.2 刷新令牌
刷新令牌由授權服務器頒發給客戶端。在OAuth 中,訪問令牌隨時可能失效。令牌有可能被用戶撤銷,也可能過期,或者其他系統導致令牌失效。訪問令牌失效後,客戶端在使用時會收到錯誤響應。當然,客戶端可以再次向資源擁有者請求權限,也可以使用刷新令牌向授權服務器請求新的訪問令牌。刷新令牌還可以讓客戶端縮小它的權限範圍。如果客戶端被授予A、B、C 三個權限範圍,但是它知道某特定請求只需要A 權限範圍,則它可以使用刷新令牌重新獲取一個僅包含A 權限範圍的訪問令牌
6 可參考的線上OAuth接口文檔
QQ互聯:使用Authorization_Code獲取Access_Token — QQ互聯WIKI
WeChat OAuth: 準備工作|微信開放文檔
支付寶 OAuth:用戶授權
Google OAuth:Using OAuth 2.0 for Web Server Applications | Google Identity Platform
Github OAuth:Authorizing OAuth Apps – GitHub Docs
7 OAuth 線上個人OAuth申請
目前可知的,支付寶以及Github均支持以給個人身份申請接入,本次示例以Github為例。
支付寶申請地址:申請網頁應用
Github申請地址:申請OAuth應用
填寫完上述內容後,提交會獲取到app_id以及app_secret,這樣我們就可以開始對接了,具體內容可以參考第6部分的文檔進行
8 Postman OAuth配置
因為第7部分我們已經申請到了github的OAuth2應用,則我們可以直接通過Postman進行快捷的接口測試,以github獲取用戶基本信息接口為例。
該接口請求為:GET //api.github.com/user
點開Get New Access Token
進行配置,相關配置可以參考第六部分提供的文檔
- AuthUrl://github.com/login/oauth/authorize
- AccessTokenUrl://github.com/login/oauth/access_token
這樣我們就可以獲取到一個訪問令牌調用接口了。