1. 引言
javascript
若是你開車去酒店赴宴,你常常會苦於找不到停車位而耽誤不少時間。是否有好辦法能夠避免這個問題呢?有的,據說有一些豪車的車主就不擔憂這個問題。豪車通常配備兩種鑰匙:主鑰匙和泊車鑰匙。當你到酒店後,只須要將泊車鑰匙交給服務生,停車的事情就由服務生去處理。與主鑰匙相比,這種泊車鑰匙的使用功能是受限制的:它只能啓動發動機並讓車行駛一段有限的距離,能夠鎖車,但沒法打開後備箱,沒法使用車內其餘設備。這裏就體現了一種簡單的「開放受權」思想:經過一把泊車鑰匙,車主便能將汽車的部分使用功能(如啓動發動機、行駛一段有限的距離)受權給服務生。java
受權是一個古老的概念,它是一個多用戶系統必須支持的功能特性。好比,Alice和Bob都是Google的用戶,那麼Alice應該能夠將本身的照片受權給Bob訪問。但請注意到,這種受權是一種封閉受權,它只支持系統內部用戶之間的相互受權,而不能支持與其餘外部系統或用戶之間的受權。好比說,Alice想使用「網易印像服務」將她的部分照片沖印出來,她怎麼能作到呢?算法
確定有人會說,Alice能夠將本身的Google用戶名和密碼告訴網易印像服務,事情不就解決了嗎?是的,但只有絕不關注安全和隱私的同窗纔會出此「絕招」。那麼咱們就來想想,這一「絕招」存在哪些問題?(1) 網易印像服務可能會緩存Alice的用戶名和密碼,並且可能沒有加密保護。它一旦遭到攻擊,Alice就會躺着中槍。(2) 網易印像服務能夠訪問Alice在Google上的全部資源,Alice沒法對他們進行最小的權限控制,好比只容許訪問某一張照片,1小時內訪問有效。(3) Alice沒法撤消她的單個受權,除非Alice更新密碼。
瀏覽器
在以Web服務爲核心的雲計算時代,像用戶Alice的這種受權需求變得日益迫切與興盛,「開放受權(Open Authorization)」也正所以而生,意在幫助Alice將她的資源受權給第三方應用,支持細粒度的權限控制,而且不會泄漏Alice的密碼或其它認證憑據。
緩存
根據應用場景的不一樣,目前實現開放受權的方法分爲兩種:一種是使用OAuth協議[1];另外一種是使用IAM服務[2]。OAuth協議主要適用於針對我的用戶對資源的開放受權,好比Google的用戶Alice。OAuth的特色是「現場受權」或「在線受權」:客戶端主要經過瀏覽器去訪問資源,受權時須要認證Alice的資源全部者身份,而且須要Alice現場審批。OAuth通常在SNS服務中普遍使用,如微博。IAM服務則不一樣,它的特色是「預先受權」或「離線受權」:客戶端主要經過REST API方式去訪問資源,資源全部者能夠預先知道第三方應用所須要的資源請求,一次受權以後,不多會變動。IAM服務通常在雲計算服務中使用,如AWS服務、阿里雲計算服務。
安全
本文主要介紹OAuth開放受權。關於以IAM服務提供的開放受權,我將在另外一篇博文中介紹。下面我來介紹OAuth 2.0協議、協議的實例化描述、安全性分析。
服務器
2. OAuth 2.0 協議
OAuth 2.0 是目前比較流行的作法,它率先被Google, Yahoo, Microsoft, Facebook等使用。之因此標註爲 2.0,是由於最初有一個1.0協議,但這個1.0協議被弄得太複雜,易用性差,因此沒有獲得普及。2.0是一個新的設計,協議簡單清晰,但它並不兼容1.0,能夠說與1.0沒什麼關係。因此,我就只介紹2.0。
2.1 協議的參與者
網絡
從引言部分的描述咱們能夠看出,OAuth的參與實體至少有以下三個:
數據結構
· RO (resource owner): 資源全部者,對資源具備受權能力的人。如上文中的用戶Alice。
app
· RS (resource server): 資源服務器,它存儲資源,並處理對資源的訪問請求。如Google資源服務器,它所保管的資源就是用戶Alice的照片。
· Client: 第三方應用,它得到RO的受權後即可以去訪問RO的資源。如網易印像服務。
此外,爲了支持開放受權功能以及更好地描述開放受權協議,OAuth引入了第四個參與實體:
· AS (authorization server): 受權服務器,它認證RO的身份,爲RO提供受權審批流程,並最終頒發受權令牌(Access Token)。讀者請注意,爲了便於協議的描述,這裏只是在邏輯上把AS與RS區分開來;在物理上,AS與RS的功能能夠由同一個服務器來提供服務。
2.2 受權類型
在開放受權中,第三方應用(Client)多是一個Web站點,也多是在瀏覽器中運行的一段JavaScript代碼,還多是安裝在本地的一個應用程序。這些第三方應用都有各自的安全特性。對於Web站點來講,它與RO瀏覽器是分離的,它能夠本身保存協議中的敏感數據,這些密鑰能夠不暴露給RO;對於javascript代碼和本地安全的應用程序來講,它原本就運行在RO的瀏覽器中,RO是能夠訪問到Client在協議中的敏感數據。
OAuth爲了支持這些不一樣類型的第三方應用,提出了多種受權類型,如受權碼 (Authorization Code Grant)、隱式受權 (Implicit Grant)、RO憑證受權 (Resource Owner Password Credentials Grant)、Client憑證受權 (Client Credentials Grant)。因爲本文旨在幫助用戶理解OAuth協議,因此我將先介紹這些受權類型的基本思路,而後選擇其中最核心、最難理解、也是最普遍使用的一種受權類型——「受權碼」,進行深刻的介紹。
2.3 OAuth協議 - 基本思路
[Figure 1: Abstract Protocol Flow]
如圖1所示,協議的基本流程以下:
(1) Client請求RO的受權,請求中通常包含:要訪問的資源路徑,操做類型,Client的身份等信息。
(2) RO批准受權,並將「受權證據」發送給Client。至於RO如何批准,這個是協議以外的事情。典型的作法是,AS提供受權審批界面,讓RO顯式批准。這個能夠參考下一節實例化分析中的描述。
(3) Client向AS請求「訪問令牌(Access Token)」。此時,Client需向AS提供RO的「受權證據」,以及Client本身身份的憑證。
(4) AS驗證經過後,向Client返回「訪問令牌」。訪問令牌也有多種類型,若爲bearer類型,那麼誰持有訪問令牌,誰就能訪問資源。
(5) Client攜帶「訪問令牌」訪問RS上的資源。在令牌的有效期內,Client能夠屢次攜帶令牌去訪問資源。
(6) RS驗證令牌的有效性,好比是否僞造、是否越權、是否過時,驗證經過後,才能提供服務。
2.4 受權碼類型的開放受權
[Figure 2: Authorization Code Flow]
如圖2所示,受權碼類型的開放受權協議流程描述以下:
(1) Client初始化協議的執行流程。首先經過HTTP 302來重定向RO用戶代理到AS。Client在redirect_uri中應包含以下參數:client_id, scope (描述被訪問的資源), redirect_uri (即Client的URI), state (用於抵制CSRF攻擊). 此外,請求中還能夠包含access_type和approval_prompt參數。當approval_prompt=force時,AS將提供交互頁面,要求RO必須顯式地批准(或拒絕)Client的這次請求。若是沒有approval_prompt參數,則默認爲RO批准這次請求。當access_type=offline時,AS將在頒發access_token時,同時還會頒發一個refresh_token。由於access_token的有效期較短(如3600秒),爲了優化協議執行流程,offline方式將容許Client直接持refresh_token來換取一個新的access_token。
(2) AS認證RO身份,並提供頁面供RO決定是否批准或拒絕Client的這次請求(當approval_prompt=force時)。
(3) 若請求被批准,AS使用步驟(1)中Client提供的redirect_uri重定向RO用戶代理到Client。redirect_uri須包含authorization_code,以及步驟1中Client提供的state。若請求被拒絕,AS將經過redirect_uri返回相應的錯誤信息。
(4) Client拿authorization_code去訪問AS以交換所需的access_token。Client請求信息中應包含用於認證Client身份所需的認證數據,以及上一步請求authorization_code時所用的redirect_uri。
(5) AS在收到authorization_code時須要驗證Client的身份,並驗證收到的redirect_uri與第3步請求authorization_code時所使用的redirect_uri相匹配。若是驗證經過,AS將返回access_token,以及refresh_token(若access_type=offline)。
若是讀者對這個流程的細節不甚清楚,那麼能夠先看第3節的一個實例化描述,而後再回來看這部份內容。
3. OAuth協議實例化描述
下面我以實例化方式來幫助讀者理解受權碼類型的受權協議的運行過程。假設:
(1) Alice有一個有效的Google賬號;
(2) Facebook.com已經在Google Authorization Server上註冊了Client身份,已經得到(client_id, client_secret),注意client_secret是Client與AS之間的一個共享密鑰。
(3) Alice想受權Facebook.com查看她的聯繫人列表(https://www.google.com/m8/feeds)。
圖3展現了Alice、Facebook.com、Google資源服務器、以及Google OAuth受權服務器之間的協議運行過程。
[Figure 3: An Instance of Authorization Code Flow]
//若字體沒法看清,請單擊右鍵->選擇查看原圖
協議所涉及到的細節都已經在圖3上了,因此不打算再作詳細介紹了。若看懂了此圖,OAuth2.0就理解了。
讀者請注意,在步驟(4)中,Client須要拿「受權碼」去換「受權令牌」時,Client須要向AS證實本身的身份,即證實本身就是步驟(2)中Alice批准受權時的Grantee。這個身份證實的方法主要有兩種(圖3中使用了第1種):
(1) 經過https直接將client_secret發送給AS,由於client_secret是由Client與AS所共享,因此只要傳送client_secret的信道安全便可。
(2) 經過消息認證碼來認證Client身份,典型的算法有HMAC-SHA1。在這種方式下,Client無需傳送client_secret,只需發送消息請求的signature便可。因爲不須要向AS傳遞敏感數據,因此它只須要使用http便可。
此外, 在步驟(2)中,Google受權服務器須要認證Alice的RO身份,並提供受權界面給Alice進行受權審批。今天Google提供的實例如圖四、圖5所示,僅供讀者理解OAuth這種「現場受權」或"在線受權"的含義。
[Figure 4: RO's Identity Authentication]
[Figure 5: RO's Authorization Decision]
4. OAuth設計上的安全性考慮
4.1 爲什麼引入authorization_code?
協議設計中,爲何要使用authorization_code來交換access_token?這是讀者容易想到的一個問題。也就是說,在協議的第3步,爲何不直接將access_token經過重定向方式返回給Client呢?好比:
HTTP/1.1 302
Location:
https://www.facebook.com/?access_token=ya29.AHES6ZSXVKYTW2VAGZtnMjD&token_type=Bearer&expires_in=3600
若是直接返回access_token,協議將變得更加簡潔,並且少一次Client與AS之間的交互,性能也更優。那爲什麼不這麼設計呢?協議文檔[1]中並無給出這樣設計的理由,但也不難分析:
(1) 瀏覽器的redirect_uri是一個不安全信道,此方式不適合於傳遞敏感數據(如access_token)。由於uri可能經過HTTP referrer被傳遞給其它惡意站點,也可能存在於瀏覽器cacher或log文件中,這就給攻擊者盜取access_token帶來了不少機會。另外,此協議也不該該假設RO用戶代理的行爲是可信賴的,由於RO的瀏覽器可能早已被攻擊者植入了跨站腳本用來監聽access_token。所以,access_token經過RO的用戶代理傳遞給Client,會顯著擴大access_token被泄露的風險。 但authorization_code能夠經過redirect_uri方式來傳遞,是由於authorization_code並不像access_token同樣敏感。即便authorization_code被泄露,攻擊者也沒法直接拿到access_token,由於拿authorization_code去交換access_token是須要驗證Client的真實身份。也就是說,除了Client以外,其餘人拿authorization_code是沒有用的。 此外,access_token應該只頒發給Client使用,其餘任何主體(包括RO)都不該該獲取access_token。協議的設計應能保證Client是惟一有能力獲取access_token的主體。引入authorization_code以後,即可以保證Client是access_token的惟一持有人。固然,Client也是惟一的有義務須要保護access_token不被泄露。
(2) 引入authorization_code還會帶來以下的好處。因爲協議須要驗證Client的身份,若是不引入authorization_code,這個Client的身份認證只能經過第1步的redirect_uri來傳遞。一樣因爲redirect_uri是一個不安全信道,這就額外要求Client必須使用數字簽名技術來進行身份認證,而不能用簡單的密碼或口令認證方式。引入authorization_code以後,AS能夠直接對Client進行身份認證(見步驟4和5),並且能夠支持任意的Client認證方式(好比,簡單地直接將Client端密鑰發送給AS)。
在咱們理解了上述安全性考慮以後,讀者也許會有豁然開朗的感受,懂得了引入authorization_code的妙處。那麼,是否是必定要引入authorization_code才能解決這些安全問題呢?固然不是。筆者將會在另外一篇博文給出一個直接返回access_token的擴展受權類型解決方案,它在知足相同安全性的條件下,使協議更簡潔,交互次數更少。
4.2 基於Web安全的考慮
OAuth協議設計不一樣於簡單的網絡安全協議的設計,由於OAuth須要考慮各類Web攻擊,好比CSRF (Cross-Site Request Forgery), XSS (Cross Site Script), Clickjacking。要理解這些攻擊原理,讀者須要對瀏覽器安全(eg, Same Origin Policy, 同源策略)有基本理解。好比,在redirect_uri中引入state參數就是從瀏覽器安全角度考慮的,有了它就能夠抵制CSRF攻擊。若是沒有這個參數,攻擊者即可以在redirect_uri中注入攻擊者提供的authorization_code或access_token,結果可能致使Client訪問錯誤的資源(好比,將款項匯到一個錯誤的賬號)。
基於Web安全的考慮,OAuth協議文檔中已經有了比較全面的闡述,因此我不打算在此文中進行展開,有興趣的讀者請參考[1]。
5. 結語
本文對OAuth 2.0 開放受權協議及其設計上的安全性考慮作了一個基本的介紹,但願能給參與安全協議設計和開發的同窗起到一點幫助。
參考文獻:
[1] Hammer-Lahav, E., Recordon, D., and D. Hardt, "The OAuth 2.0 Authorization Framework", draft-ietf-oauth-v2-31 (work in progress), June 2012.
[2] http://aws.amazon.com/iam/