1. 程式人生 > >認證授權那點事兒 —— OAuth 2.0

認證授權那點事兒 —— OAuth 2.0

OAuth 2.0 —— 開放授權協議,對應的規範檔案RFC-6749早在2012年便成形,所以這並不是一個新的技術(你問我為啥研究這個,我也想吟一首詩啊。。。組織上就是這樣決定的),但由於其必不可少的價值,在今天的網路上已經得到了廣泛的應用。

OAuth2.0認證是要在不同的應用之間打通互信,互信的目的是為了實現一定程度上的使用者資料分享,沒資料的一方到有資料的一方拿資料,並且在時間尺度上,資料的分享是受控的。

比如你在微信上開啟小程式,小程式會向微信索要你的基本資訊;比如你用馬克飛象作為印象筆記的客戶端,馬克飛象會向印象筆記索要你的賬戶資訊,以及閱讀、建立、刪除、修改筆記等的許可權;比如你用github賬號登入leetcode,後者會去前者索要你的賬戶資訊 ……

在這些場景裡都涉及三方:使用者、沒資料的應用、有資料的應用(授權、資源)。OAuth2.0規範就規定了三方如何互動,完成許可權的授予,獲取資料的過程。整體邏輯過程如下圖

整個授權框架中包含了4種授權模式:

  • 授權碼模式
  • 隱式許可模式
  • 密碼模式
  • 客戶端模式

在詳述4種模式之前,首先需要注意,第三方應用需要先到資源持有應用處註冊身份,提交回調URI,註冊成功後,得到標識身份的 Client ID 和 Client Secret。資源持有者也可以趁這個階段對第三方應用進行安全稽核。
這裡寫圖片描述

Client Type, 根據客戶端與授權伺服器進行安全認證的能力,分為 Confidential(機密客戶端)和 Public(公開客戶端)。
Client ID

Client Secret,應用的身份標識。
Registered Redirect URIs,回撥地址,後面會用到。

授權碼模式

授權碼模式是四種模式中最嚴密的,主要用於web應用,我們來看看整體邏輯圖
這裡寫圖片描述

下面我按圖上的順序一步步說明。

  1. 我在瀏覽器上打開了LeetCode網頁,並且發現可以用github賬戶來登入,於是,我點選了章魚標誌。這個時候,就被LeetCode導向了github的認證頁面,並且攜帶了幾個必要引數。

    Client ID,表明第三方應用自己的身份,即這個請求是從LeetCode過來的。
    Scope,表明要請求哪些使用者資訊,使用者名稱、密碼、郵箱、頭像……
    Redirect URI

    ,應用註冊時填入的回撥URI之一,授權伺服器會檢查,必須嚴格匹配。
    State,一個不容意被猜到的隨機數,用於第3步確認收到的code是自己請求的,避免 CSRF 攻擊。

  2. 現在打開了github的認證頁面(如果沒有事先登入,github會要求我先登入github賬號),github頁面提示是否授權給LeetCode,點選是,則進入下一步;否則,流程結束。

  3. github呼叫回撥URI(這個URI就是最初註冊時提交的URI之一),傳回授權碼,把state也傳回。這時,授權碼就到了第三方應用的後臺伺服器。這一步之後的互動,在瀏覽器上就看不到了,由兩個應用的後臺伺服器完成。

    Authorization Code,授權碼,一個臨時的隨機串。
    State,第1步傳過來的state。

  4. 拿到code,並比對了state後,LeetCode伺服器就拿著這個code去換訪問令牌。

    Client ID & Client Secret,表明自己的身份。
    Redirect URI,獲取授權碼時提交的回撥地址,授權伺服器會檢查是不是一致。
    Authorization Code,前面拿到的授權碼。

  5. 授權伺服器認證了客戶端身份,並檢查回撥地址通過後,呼叫回撥URI回傳訪問令牌。

    Token Type,值為 Bearer Token。
    Access Token,訪問令牌,訪問資源的憑據。
    Scope,許可權範圍。
    Expires In,令牌有效時間,單位秒。
    Refresh Token,可選引數,使用者更新訪問令牌。

  6. 現在LeetCode可以拿著令牌去github獲取我的賬戶資訊了,從瀏覽器上看,我順利登入了LeetCode。

回過頭來看一下,為什麼先發一個授權碼,不直接發訪問令牌?

因為直接發令牌,那麼到達使用者瀏覽器的就是令牌。而拿著令牌去訪問資源是不需要認證的(別問為什麼,就是這樣的設計和用法)。如果本地環境不可信,那麼讓第三方應用伺服器和資源伺服器去互動,完成code到token的轉換是更安全的選擇。

即使Authorization Code被另一個應用竊取,並且該應用在授權伺服器上合法,也不能使用這個授權碼,因為授權伺服器會檢查帶著授權碼過來換token的應用是不是最初申請該授權碼的應用。

而且,授權碼只能被使用一次,當在本地被竊取了以後,如果你使用一次code,攻擊者使用一次code,不管誰先誰後,第二次使用code將導致授權伺服器終止該code及已下發的token的有效性。

授權碼模式被使用的最多,以上講解只是列出了規範中的主要引數,編碼時具體實現還要看各廠是如何落地規範的,可以搜github、微信、google等的介面文件。

隱式許可模式

隱式許可模式和授權碼模式最大的不同就是沒有授權碼互動這一環節,它主要用於依附於瀏覽器的駐於使用者側的應用,如JavaScript應用。
這裡寫圖片描述

還是從圖說起

  1. 和授權碼模式一樣。

  2. 和授權碼模式一樣。

  3. 使用者授權通過後,呼叫回撥URI,直接就傳回訪問令牌。

    Redirect URI,指向第三方應用的資源伺服器。
    Fragment with Access Token, etc.,在URI的fragment部分(即#後的部分)攜帶訪問令牌等引數,只會被留在瀏覽器本地,裡面包含了和授權碼模式第5步中相同引數。

  4. 訪問第三方應用的資源伺服器,請求從fragment引數中提取訪問令牌的指令碼,由於http規範,請求中不會攜帶fragment部分。

  5. 返回解析指令碼,並提取出訪問令牌。

  6. 拿著訪問令牌去獲取資源。

這裡看到4、5的時候,困惑了很久,為什麼要特地去第三方應用的伺服器上取解析指令碼,有什麼安全考量。SO上某個5K聲望的仁兄的回答是:沒看出什麼考慮,只是在本地放指令碼和去伺服器取指令碼中選了一種方式。我看,是不是因為反正都會用到 Redirect URI,就順便取個指令碼,不然糟蹋了這一來一回。最後訪問令牌留在了駐使用者側的應用裡。考慮到這種模式不嚴瑾,行業最佳實踐建議採用授權碼模式或PKCE模式。

密碼模式

密碼模式就是使用者把使用者名稱和密碼都放心地給到第三方應用(如山一般厚重的信任),第三方應用以使用者的名字去認證並獲取資源。規範中規定了應用不應該儲存使用者輸入的使用者名稱和密碼。此種模式多見於資源伺服器自家的應用。
這裡寫圖片描述

客戶端模式

第三方應用證明自己的身份,以自己的身份去獲取一些公共的資源。這種模式我就不細解釋了,引數用途和前面的一致。
這裡寫圖片描述

基本就是這樣了,下次再講講 PKCE、Device Code。

如理解有誤,歡迎各位老鐵拍磚。



參考:
https://oauth.net/2/
https://tools.ietf.org/html/rfc6749
https://getpocket.com/a/read/2216790858