OAuth 2.0 協議是一種三方受權協議,目前大部分的第三方登陸與開放受權都是基於該協議的標準或改進實現。OAuth 1.0 版本於 2007 年發佈,2.0 版本則在 2011 年發佈,其中 2.0 版本取消了全部 token 的加密過程,並簡化了受權流程,但因強制使用 HTTPS 協議,被認爲安全性高於以前的版本。html
項目地址: https://github.com/plotor/oau...
爲了讓你對 OAuth 2.0 協議有一個總體上的感知,這裏先設置一個小場景對協議的交互過程進行模擬。話說阿冰在花果山上有幾畝果園,種了各類各樣的水果,有蘋果、荔枝、西瓜等等,並由管理員老王進行看管。git
夏天到了,果園裏的的水果漲勢喜人,阿冰的好朋友小桂子想去果園摘一些西瓜和荔枝解解饞,因而小桂子提着果籃吭哧吭哧就跑到了花果山,結果被管理員老王一把攔住,呵斥道:「要摘水果,必須通過阿冰的贊成,快出示相關憑證」,小桂子固然沒有,還納悶去哪搞什麼鬼憑證。這時候老王拎着小桂子來到了一個茅草房前,只見上面寫着「花果山街道辦事處」。老王告訴小桂子這裏能夠開具憑證。github
小桂子來到櫃檯前,業務人員詢問了其姓名,並要求出示身份證件。覈實身份以後,業務人員詢問小桂子要去誰的果園,採摘什麼水果?小桂子如實答覆,不一會只見業務人員打印出了一張憑證,並將其丟入一個「時光通道」,憑證上寫着:算法
小桂子請求在您的果園採摘如下水果: - 採摘您的西瓜 2 個 - 採摘您的荔枝 3 斤
通道那頭的阿冰收到憑證以後蓋上本身的印章以示贊成,而後將其投回了「時光通道」。最終小桂子拿到了通過阿冰受權的憑證,跳着奔向果園。管理員老王驗證了小桂子出示的憑證,並摘了 2 個西瓜和 3 斤荔枝交到了小桂子手上。json
一個星期後,小桂子嘴又饞了,拿着上次的憑證直奔花果山,到了果園門口又被老王攔住了,老王說:「你這憑證已通過期了,必須再次請求阿冰受權」。小桂子滿臉委屈,嘟囔着:「不就想吃倆西瓜嘛,怎麼就管的這麼嚴」。瀏覽器
小編不才,實在編不下去了,就這樣結束吧。。。緩存
在這個故事中,憑證限制了小桂子是否能夠得到水果,以及得到哪些水果,每種水果多少斤。此外憑證還具有生命週期,超出憑證範圍的請求都會被老王拒絕。經過憑證,小桂子能夠獲取到本身想要的水果,阿冰也不須要親臨花果山,在作好果園管理的同時,又不影響阿冰的生活。安全
回到正題,對於 OAuth 2.0 協議(如下簡稱 OAuth 協議),我相信大部分讀者都有所接觸,最多見的就是使用微信登陸第三方應用。的確,OAuth 協議被普遍應用於第三方受權登陸中,藉助第三方登陸可讓用戶免於再次註冊之苦,支持第三方登陸也對這些網站、APP 起到了積極的做用,免去了複雜的註冊過程,用戶體驗更佳。這樣在提升留存率的同時,也更加易於收集用戶的一些非敏感信息等,另外還能夠藉助一些社交類的第三方賬號進行站點推廣。服務器
做爲第三方登陸服務提供方,其核心矛盾點是 既要讓用戶在對接服務的 APP 上完成登陸,同時還不能讓該 APP 拿到用戶的密碼憑證 。解決這一矛盾的利器就是 token(中文譯爲令牌),而 OAuth 協議的最終目的就是給第三方應用下發 token,它記錄了用戶的登陸或受權狀態。經過將 token 下發給第三方應用,既能讓 APP 登陸並拿到用戶許可的數據,也能夠將用戶的密碼憑證緊緊拽在服務本身手裏。微信
上面的論述可能側重了第三方登陸,實際上登陸只是一個受權的過程,OAuth 2.0 協設計的目的在於開放受權。對於一個應用,其最終目的仍是在於拿到用戶存儲在資源服務器上的用戶數據,因此登陸受權還只是第一步,後續 APP 還須要攜帶 token 去資源服務器請求用戶數據,這個時候是一個鑑權的過程,OAuth 協議的主要目的在於受權,至於鑑權,實現上主要是仍是對請求傳遞過來的 token 進行解析和驗證,這一塊相對要簡單一些,因此本文主要講解 OAuth 受權的過程。
受保護資源所屬的實體,好比資源的全部人,下文的用戶即爲資源全部者。
託管受保護資源的服務器,可以響應持有訪問令牌的資源訪問請求,能夠與受權服務器是同一臺服務器,也能夠分開。
客戶端是 OAuth 服務的接入方,其目的是但願請求用戶存儲在資源服務器上的受保護資源。
受權服務器的主要職責是驗證資源全部者的身份,並依據資源全部者的許可向客戶端下發訪問令牌。
在以前的故事中,果園中的水果就是資源,而資源全部者是阿冰,果園能夠看作是資源服務器,小桂子就是客戶端,而街道辦事處是整個流程的受權中心,也就是上面的受權服務器。
還記得故事中老王問小桂子要的憑證嗎?憑證限制了小桂子只能摘 2 個西瓜和 3 斤荔枝,而且憑證仍是具有生命週期的,一個星期以後小桂子再拿着過時的憑證老王也不認了。
實際上故事中的憑證對應的是 OAuth 2.0 中的訪問令牌,訪問令牌是在用戶受權許可下,受權服務器下發給客戶端的一個受權憑證,該令牌所要表達的意思是「 用戶授予該 APP 在多少時間範圍內容許訪問哪些與本身相關的服務或數據 」,因此訪問令牌主要在 時間範圍 和 權限範圍 兩個維度進行控制,此外訪問令牌對於客戶端來講是非透明的,外在表現就是一個字符串,客戶端沒法知曉字符串背後所隱藏的用戶信息,所以不用擔憂用戶的密碼憑證會所以泄露。
故事中小桂子最後之因此以爲委屈是由於意識到本身須要再重複走一次受權過程,這讓小桂子以爲很麻煩,專業點說就是用戶體驗太差,解決之道就是引入刷新令牌。
刷新令牌的做用在於更新訪問令牌,訪問令牌的生命週期通常較短,這樣能夠保證在發生訪問令牌泄露時,不至於形成太壞的影響,可是訪問令牌有效期設置過短致使的反作用就是用戶須要頻繁受權,雖然能夠經過必定的機制進行靜默受權,可是頻繁的調用受權接口之於受權服務器也是一種壓力。此時能夠在下發訪問令牌的同時下發一個刷新令牌,刷新令牌的生命週期明顯長於訪問令牌,這樣在訪問令牌失效時,能夠利用刷新令牌去受權服務器換取新的訪問令牌,不過協議對於刷新令牌沒有強制規定,是否須要該令牌客戶端能夠自行選擇。
OAuth 2.0 是一類基於回調的受權協議,以受權碼模式爲例,整個受權須要分爲兩步進行,第一步下發受權碼,第二步根據受權碼請求受權服務器下發訪問令牌。OAuth 在第一步下發受權碼時,是將受權碼以參數的形式添加到回調地址後面,並以 302 跳轉的形式進行下發,這樣簡化了客戶端的操做,不須要再主動去觸發一次請求,便可進入下一步流程。
回調的設計存在必定的安全隱患,壞人能夠利用該機制引導用戶到一個惡意站點,繼而對用戶發起攻擊。對於受權服務器而言,也存在必定的危害,壞人能夠利用該機制讓受權服務器變成「肉雞」,以受權服務器爲代理請求目標地址,這樣在消耗受權服務器資源的同時,也對目標地址服務器產生 DDOS 攻擊。
爲了不上述安全隱患,OAuth 協議強制要求客戶端在註冊時填寫本身的回調地址,其目的是爲了讓回調請求可以到達客戶端本身的服務器,從而能夠走獲取訪問令牌的流程。客戶端能夠同時配置多個回調地址,並在請求受權時攜帶一個地址,服務器會驗證客戶端傳遞上來的回調地址是否與以前註冊的回調地址相同,或者前者是後者集合的一個元素,只有在知足這一條件下才容許下發受權碼,同時協議還要求兩步請求客戶端攜帶的回調地址必須一致,經過這些措施來保證回調過程可以正常到達客戶端本身的服務器,並繼續後面拿受權碼換取訪問令牌的過程。
訪問令牌自帶過時時間,能夠在時間維度上對受權進行控制,而在權限範圍上,OAuth 引入了一個 scope 的概念。Scope 能夠看作是一個對象,包含一個權限的 ID,名稱,以及描述信息等,好比 「獲取您的基本資料(頭像、暱稱)」。應該在接入 OAuth 服務時必須向服務提供方申請相應的 scope,並在請求受權時指明該參數,這些權限在用戶確認受權時,必須毫無保留的展現給用戶,以讓用戶知道本次請求須要訪問用戶的哪些數據或服務。
在以前的故事中憑證容許小桂子只能摘取 2 個西瓜和 3 斤荔枝,這裏就對應兩個 scope,這些信息是寫入到憑證(訪問令牌)中的,從而限制憑證的權限範圍。
OAuth 2.0 協議定義了 4 種受權模式,其中最具表明性的是受權碼模式,咱們將在 3.1 節中詳細介紹,這裏先簡單體會一下 OAuth 2.0 的受權流程,交互時序圖以下:
假設整個流程開始以前,用戶已經登陸,那麼整個受權流程以下:
- 客戶端請求資源全部者(用戶)受權,通常都是由受權服務器進行引導
- 資源全部者實施受權(採用 4 種受權模式中的一種),客戶端拿到用戶的受權憑證
- 客戶端攜帶用戶受權憑證請求受權服務器下發訪問令牌
- 受權服務器驗證客戶端出示的受權憑證,並下發訪問令牌
- 客戶端攜帶訪問令牌請求存儲在資源服務器上的用戶受保護資源
- 資源服務器驗證客戶端出示的訪問令牌,經過則響應客戶端的請求
整個過程當中,客戶端都沒法接觸到用戶的密碼憑證信息,客戶端經過訪問令牌請求受保護資源,用戶能夠經過對受權操做的控制來間接控制客戶端對於受保護資源的訪問權限範圍和週期。
OAuth 2.0 相對於 1.0 版本在受權模式上作了更多的細化,已定義的受權模式分爲四種:
受權碼模式在整個受權流程上與 1.0 版本最爲貼近,可是流程上仍是要簡化許多,也是 OAuth 2.0 中最標準,應用最爲普遍的受權模式。這類受權模式很是適用於具有服務端的應用,固然如今大多數 APP 都有本身的服務端,因此受權碼模式擁有最普遍的應用場景,下圖爲受權碼各個角色之間的交互時序:
整個受權流程說明以下(具體參數釋義見下文):
- 客戶端攜帶 client_id, scope, redirect_uri, state 等信息請求受權服務器下發 code
- 受權服務器驗證客戶端身份,經過則詢問用戶是否贊成受權(此時會跳轉到用戶可以直觀看到的受權頁面,等待用戶點擊確認受權)
- 假設用戶贊成受權,此時受權服務器會將 code 和 state 拼接在 redirect_uri 後面,並以 302 形式下發 code
- 客戶端攜帶 code, redirect_uri, 以及 client_secret 請求受權服務器下發 access_token
- 受權服務器驗證客戶端身份,同時驗證 code,以及 redirect_uri 是否與第一步相同,經過則下發 access_token,並選擇性下發 refresh_token
受權碼是受權流程的一箇中間臨時憑證,是對用戶確認受權這一操做的一個短暫性表徵,其生命週期通常較短,協議建議最大不要超過 10 分鐘,在這一有效時間內,客戶端能夠經過受權碼去受權服務器請求換取訪問令牌,受權碼應該採起防重放措施。
請求參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
response_type | 必須 | 對於受權碼模式來講 response_type=code |
client_id | 必須 | 客戶端 ID,用於標識一個客戶端,在註冊應用時生成 |
redirect_uri | 可選 | 受權回調地址,具體參見 2.2.3 小節 |
scope | 可選 | 權限範圍,用於對客戶端的權限進行控制,若是客戶端沒有傳遞該參數,那麼服務器則以該應用被許可的全部權限代替 |
state | 推薦 | 用於維持請求和回調過程當中的狀態,防止 CSRF攻擊,服務器不對該參數作任何處理,若是客戶端攜帶了該參數,則服務器在響應時原封不動的進行返回 |
請求參數示例:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com
客戶端攜帶上述參數請求受權服務器,受權服務器會驗證客戶端的身份以及相關參數,並在確認用戶已登陸的前提下彈出受權頁詢問用戶是否贊成受權,若是用戶贊成則會將受權碼(code)和 state 信息添加到回調地址後面,並以 302 的形式下發。
成功響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
code | 必須 | 受權碼,受權碼錶明用戶確認受權的暫時性憑證,推薦最大生命週期不超過 10 分鐘 |
state | 可選 | 若是客戶端傳遞了該參數,則必須原封不動返回 |
成功響應示例:
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
若是請求參數錯誤,或者服務器端響應錯誤,那麼須要將錯誤信息添加在回調地址後面,一樣以 302 形式下發(回調地址錯誤,或客戶端標識無效除外)。
錯誤響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
error | 必須 | 錯誤代碼 |
error_description | 可選 | 具有可讀性的錯誤描述信息 |
error_uri | 可選 | 錯誤描述信息頁面地址 |
state | 可選 | 若是客戶端傳遞了該參數,則必須原封不動返回 |
錯誤響應示例:
HTTP/1.1 302 Found Location: https://client.example.com/cb?error=access_denied&state=xyz
受權服務器的受權端點在以 302 形式下發 code 以後,用戶 User-Agent,好比瀏覽器,將攜帶對應的 code 回調請求用戶指定的 redirect_url,這個地址應該可以保證請求打到應用服務器的對應接口,該接口能夠由此拿到 code,並附加相應參數請求受權服務器的令牌端點,受權端點驗證 code 和相關參數,驗證經過則下發 access_token。
請求參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
grant_type | 必須 | 對於受權碼模式 grant_type=authorization_code |
code | 必須 | 上一步驟獲取的受權碼 |
redirect_uri | 必須 | 受權回調地址,具體參見 2.2.3 小節,若是上一步有設置,則必須相同 |
client_id | 必須 | 客戶端 ID,用於標識一個客戶端,在註冊應用時生成 |
若是在註冊應用時有下發客戶端憑證信息(client_secret),那麼客戶端必須攜帶該參數以讓受權服務器驗證客戶端的真實性。針對客戶端憑證須要多說的一點就是不能將其存儲或傳遞到客戶端,客戶端沒法保證 client_secret 的安全,應該始終將其存儲在應用的服務器端,當下發受權碼回調請求到應用服務器時,在服務器端攜帶上 client_secret 繼續請求下發令牌。
請求參數示例:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
受權服務器須要驗證客戶端的真實性,以及是否與以前請求受權碼的客戶端屬同一個(請求受權時的信息能夠記錄在受權碼中,或以受權碼爲 key 創建緩存),受權服務器還要保證受權碼處於生命週期內,且只能被使用一次。驗證經過以後,受權服務器生成 access_token,並選擇性下發 refresh_token,OAuth 2.0 協議明確了 token 的下發策略,對於 token 的生成策略沒有作太多說明,不過相關 RFC 補充文檔爲生成 token 提供了指導,目前主要的 token 有 BEARER、MAC 等類型。
成功響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌類型,好比 BEARER、MAC 等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒爲單位,表示令牌下發後多久時間過時,若是沒有指定該項,則使用默認值 |
refresh_token | 可選 | 刷新令牌,選擇性下發,參見 2.2.2 |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
最後訪問令牌以 JSON 格式響應,並要求指定響應首部 Cache-Control: no-store
和 Pragma: no-cache
。
成功響應示例:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }
錯誤響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
error | 必須 | 錯誤代碼 |
error_description | 可選 | 具有可讀性的錯誤描述信息 |
error_uri | 可選 | 錯誤描述信息頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_request" }
受權碼受權模式是 OAuth 2.0 協議已定義 4 種模式中最嚴謹的模式,其他 3 中模式都是創建在一些特殊場景下,並對這些場景作了一些妥協和優化。受權碼受權流程分爲兩步走,將用戶受權與下發訪問令牌分開,這給受權帶來了更多的靈活性,正常受權過程當中必須通過用戶登陸這一步驟,在用戶已登陸的前提下,能夠直接詢問用戶是否贊成受權,可是在一些場景下,好比內部走 SSO(單點登陸)的應用集成了基於 OAuth 受權的第三方應用,這個時候在 OAuth 受權登陸第三方應用時,用戶體驗較好的流程是不須要用戶再次輸入用戶名和密碼的,這就須要將外圍 APP 的登陸態傳遞給嵌套的應用,可是這樣是存在安全問題的,用戶的登陸態必須把握在走 SSO 登陸流程的應用手上,這樣的場景下受權碼受權模式的兩步走流程就能夠知足在不交出用戶登陸態的狀況下,無需再次登陸便可受權。
內部應用能夠拿着第三方應用的 client_id 等信息代替第三方應用去請求獲取 code,由於本身持有用戶的登陸態,因此過程當中無需用戶再次輸入用戶名和密碼,拿到 code 以後將其交給第三方應用,第三方應用利用 code 和本身的 client_secret 信息去請求受權服務器下發 token,整個流程內部應用不須要交出本身持有的用戶登陸態,第三方應用也無需交出本身的 client_secret 信息,最終卻可以實如今保護用戶密碼憑證的前提下無需再次登陸便可完成整個受權流程。
對於一些純客戶端應用,每每沒法妥善的保管其客戶端憑證,同時由於沒有服務器端,因此沒法向受權服務器傳遞憑證信息,而且純客戶端應用在請求交互上要弱於有服務器的應用,這時候減小交互可讓應用的穩定性和用戶體驗更好,隱式受權模式是對這一應用場景的優化。
隱式受權模式在安全性上要弱於受權碼模式,由於沒法對當前客戶端的真實性進行驗證,同時對於下發的 access_token 存在被同設備上其它應用竊取的風險,爲了下降這類風險,隱式受權模式強制要求不能下發 refresh_token,這一強制要求的另一個考量是由於 refresh_token 的生命週期較長,而客戶端沒法安全的對其進行存儲和保護。下圖爲受權碼各個角色之間的交互時序:
整個受權流程說明以下:
- 客戶端攜帶 client_id, scope, redirect_uri, state 等信息請求受權服務器下發 access_token
- 受權服務器驗證客戶端身份,經過則詢問用戶是否贊成受權(此時會跳轉到用戶可以直觀看到的受權頁面,等待用戶點擊確認受權)
- 假設用戶贊成受權,此時受權服務器會將 access_token 和 state 等信息以 URI Fragment 形式拼接在 redirect_uri 後面,並以 302 形式下發
- 客戶端利用腳本解析獲取 access_token
不一樣於受權碼模式的分兩步走,隱式受權碼模式一步便可拿到訪問令牌。
請求參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
response_type | 必須 | 對於受權碼模式 response_type=token |
client_id | 必須 | 客戶端 ID,用於標識一個客戶端,在註冊應用時生成 |
redirect_uri | 可選 | 受權回調地址,具體參見 2.2.3 小節 |
scope | 可選 | 權限範圍,用於對客戶端的權限進行控制,若是客戶端沒有傳遞該參數,那麼服務器則以該應用被許可的全部權限代替 |
state | 推薦 | 用於維持請求和回調過程當中的狀態,防止 CSRF攻擊,服務器不對該參數作任何處理,若是客戶端攜帶了該參數,則服務器在響應時原封不動的返回 |
請求參數示例:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com
成功響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌類型,好比 BEARER,MAC 等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒爲單位,表示令牌下發後多久時間過時,若是沒有指定該項,則使用默認值 |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
state | 可選 | 若是客戶端傳遞了該參數,則必須原封不動返回 |
隱式受權模式不下發刷新令牌,訪問令牌以 URI Fragment 的形式拼接在受權回調地址後面以 302 形式下發,並要求指定響應首部 Cache-Control: no-store
和 Pragma: no-cache
。
成功響應示例:
HTTP/1.1 302 Found Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
錯誤響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
error | 必須 | 錯誤代碼 |
error_description | 可選 | 具有可讀性的錯誤描述信息 |
error_uri | 可選 | 錯誤描述信息頁面地址 |
state | 可選 | 若是客戶端傳遞了該參數,則必須原封不動返回 |
受權服務器將上述元素以 URI Fragment 形式拼接在受權回調地址後面以 302 形式下發(redirect_uri 或 client_id 錯誤除外)。
錯誤響應參數示例:
HTTP/1.1 302 Found Location: https://client.example.com/cb#error=access_denied&state=xyz
資源全部者密碼憑證受權模式創建在資源全部者充分信任客戶端的前提下,由於該模式客戶端能夠拿到用的登陸憑證,從而在用戶無感知的狀況下完成整個受權流程,畢竟都有用戶的登陸憑證了,再彈窗讓用戶確認受權也是畫蛇添足。
這裏可能有一個比較疑惑的地方是既然已經拿到了用戶的登陸憑證,爲何還須要繞一大圈子走 OAuth 受權,拿到令牌再去請求用戶的受保護資源呢?實際中事情可能並不會這麼簡單,拿到用戶登陸憑證的不必定是用戶自己,並且這裏協議指的用戶登陸憑證是用戶的用戶名和密碼,實際中還能夠是走 SSO 登陸下發的 token,token 在持有權限上要小於等於用戶的用戶名和密碼,這是從客戶端角度出發。對於資源服務器來講,有些敏感數據須要在用戶級別作權限控制,對於服務級別的控制粒度太粗,因此這些服務每每須要服務攜帶 access_token 來請求某一個用戶的敏感數據。
舉個例子來講,好比有一個服務是獲取某個用戶的通信錄,這是用戶十分敏感的數據,且通常只能授予內部應用,若是是在服務級別進行控制,那麼只要拿到服務權限,該應用能夠請求獲取任何一個用戶的通信錄數據,這是一件十分危險的事情。若是基於 access_token 作鑑權,那麼就能夠將粒度控制在用戶級別,前面講的兩種受權方式在這裏應用時都有一個共同的缺點,須要彈出受權頁讓用戶確認受權,要知道這樣的場景每每是發生在內部應用裏面,內部應用是能夠持有用戶登陸態的,這裏的確認受權對於一個用戶體驗好的 APP 來講就應該發生在用戶登陸時,經過用戶協議等方式直接告訴用戶,從而讓用戶在一次登陸過程當中可讓應用拿到用戶的登陸態和訪問令牌。資源全部者密碼憑證受權模式的交互時序以下:
整個受權流程說明以下:
- 用於授予客戶端登陸憑證(好比用戶名和密碼信息,亦或是 token)
- 客戶端攜帶用戶的登陸憑證和 scope 等信息請求受權服務器下發 access_token
- 受權服務器驗證用戶的登陸憑證和客戶端信息的真實性,經過則下發 access_token,並選擇性下發 refresh_token
用於登陸憑證如何傳遞給客戶端這一塊協議未作說明,實際中該類受權通常用於內部應用,這類應用的特色就是爲用戶提供登陸功能,當用戶登陸以後,這類應用也就持有了用戶的登陸態,能夠是用戶登陸的 session 標識,也能夠是走 SSO 下發的 token 信息。
請求參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
grant_type | 必須 | 對於本模式 grant_type=password |
username | 必須 | 用戶名 |
password | 必須 | 用戶密碼 |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
若是在註冊應用時有下發客戶端憑證信息(client_secret),那麼客戶端必須攜帶該參數以讓受權服務器驗證客戶端的真實性。
請求參數示例:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=password&username=johndoe&password=A3ddj3w
成功響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌類型,好比 BEARER、MAC 等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒爲單位,表示令牌下發後多久時間過時,若是沒有指定該項,則使用默認值 |
refresh_token | 可選 | 刷新令牌,選擇性下發,參見 2.2.2 |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
最後訪問令牌以 JSON 格式響應,並要求指定響應首部 Cache-Control: no-store
和 Pragma: no-cache
。
成功響應參數示例:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }
錯誤響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
error | 必須 | 錯誤代碼 |
error_description | 可選 | 具有可讀性的錯誤描述信息 |
error_uri | 可選 | 錯誤描述信息頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_request" }
客戶端憑證受權模式基於客戶端持有的證書去請求用戶的受保護資源,若是把這裏的受保護資源定義得更加寬泛一點,好比說是對一個內網接口權限的調用,那麼這類受權方式能夠被改造爲內網權限驗證服務。客戶端憑證受權模式的交互時序以下:
整個受權流程說明以下:
- 客戶端攜帶客戶端憑證和 scope 等信息請求受權服務器下發 access_token
- 受權服務器驗證客戶端真實性,經過則下發 access_token
請求參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
grant_type | 必須 | 對於本模式 grant_type=client_credentials |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
請求參數示例:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=client_credentials
成功響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌類型,好比 BEARER、MAC 等等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒爲單位,表示令牌下發後多久時間過時,若是沒有指定該項,則使用默認值 |
scope | 可選 | 權限範圍,若是最終下發的訪問令牌對應的權限範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該參數指定說明 |
最後訪問令牌以 JSON 格式響應,並要求指定響應首部 Cache-Control: no-store
和 Pragma: no-cache
。
成功響應參數示例:
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "example_parameter":"example_value" }
錯誤響應參數說明:
名稱 | 是否必須 | 描述信息 |
---|---|---|
error | 必須 | 錯誤代碼 |
error_description | 可選 | 具有可讀性的錯誤描述信息 |
error_uri | 可選 | 錯誤描述信息頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "error":"invalid_request" }
本文介紹了 OAuth 2.0 受權協議的理論知識,OAuth 2.0 被普遍應用於第三方受權登陸,不少其它的協議均可以基於該協議進行改造,好比前面提到的 SSO,做爲開發人員,仍是建議對該協議或多或少有些瞭解。若是要本身實現一個受權和鑑權服務,該協議爲咱們繪製指明瞭思路,可是還有不少細節實現須要咱們再去查閱各類資料和實踐。
關於 token 的生成最後再補充一點,OAuth 2.0 協議只是一筆帶過的說它是一個字符串,用於表示特定的權限、生命週期等,卻沒有明確闡述 token 的生成策略,以及如何去驗證一個 token。協議不去詳細闡述,我的以爲是由於這一塊是與具體業務綁定的,沒法徹底作到抽象,而且在這一塊去作詳細的規定,意義也不大。
Token 本質上就是對用戶受權這一操做在時間和權限範圍兩個維度上的一個表徵,協議能夠對 token 的傳遞和基本驗證作相應規定,可是具體的一個 token 包含哪些元素,採用什麼樣的生成算法仍是須要由本身去把握。一些文檔,好比參考文獻 3 和 4 都爲 token 的生成進行了擴展說明,鑑於篇幅,再也不展開。