淺談SAML, OAuth, OpenID和SSO, JWT和Session

前言

一般爲了弄清楚一個概念,咱們須要掌握十個概念。在判斷 JWT(JsonWebToken) 是否能代替 session 管理以前,咱們要了解什麼是 token,以及 access tokenrefresh token 的區別。python

瞭解什麼是 OAuth,什麼是 SSOSSO 下不一樣策略 OAuthSAML 的不一樣,以及 OAuthOpenID 的不一樣,更重要的是區分 authorisationauthentication算法

最後咱們引出 JSON WEB TOKEN,聊聊 JWT 在 Session 管理方面的優點和劣勢,同時嘗試解決這些劣勢,看當作本和代價有多少。編程

正文

本文關於 OAuth 受權API 調用實例都來自 Google APIjson

關於Token

Token 即便是在計算機領域中也有不一樣的定義,這裏咱們說的 token,是指 訪問資源 的憑據。例如當你調用 Google API 時,須要帶上有效 token 來代表你請求的 合法性。這個 TokenGoogle 給你的,這表明 Google 給你的 受權 使得你有能力訪問 API 背後的 資源後端

請求 API 時攜帶 token 的方式也有不少種,經過 HTTP Header 或者 url 參數或者 google 提供的類庫均可以:api

  • HTTP Header
GET /drive/v2/files HTTP/1.1

Authorization: Bearer <token>
Host: www.googleapis.com/
複製代碼
  • URL參數
GET https://www.googleapis.com/drive/v2/files?token=<token>
複製代碼
  • Python函數庫
from googleapiclient.discovery import build
drive = build('drive', 'v2', credentials=credentials)
複製代碼

更具體的說,上面用於調用 APItoken,咱們稱爲細分爲 access token。一般 access token 是有 有效期限 的,若是 過時 就須要 從新獲取。那麼如何從新獲取?先看看第一次獲取 token 的流程是怎樣的:瀏覽器

  1. 首先須要向 Google API 註冊一個應用程序,註冊完畢以後就會拿到 認證信息credentials)包括 IDsecret。不是全部的程序類型都有 secret緩存

  2. 接下來就要向 Google 請求 access token。這裏先忽略一些細節,例如請求參數(固然須要上面申請到的 secret)。重要的是,若是你想訪問的是 用戶資源,這裏就會提醒用戶進行 受權bash

  3. 若是 用戶受權 完畢。Google 就會返回 access token。又或者是返回 受權代碼authorization code),再經過代碼取得 access token服務器

token 獲取到以後,就可以帶上 token 訪問 API 了。

流程以下圖所示:

注意:在第三步經過 authorization code 兌換 access token 的過程當中,Google 並不會僅僅返回 access token,還會返回額外的信息,這其中和以後更新相關的就是 refresh token

一旦 access token 過時,你就能夠經過 refresh token 再次請求 access token

以上只是大體的流程,而且故意省略了一些額外的概念。好比更新 access token 固然也能夠不須要 refresh token,這要根據你的 請求方式 和訪問的 資源類型 而定。

這裏又會引發另外的兩個問題:

  1. 若是 refesh token 也過時了怎麼辦?這時就須要用戶 從新登錄受權

  2. 爲何要區分 refresh tokenaccess token?若是合併成一個 token 而後把 過時時間 調整的 更長,而且每次 失效 以後用戶 從新登錄受權 就行了?這個問題會和後面談的相關概念有關,後面會給予解釋說明。

OAuth 2.0

從獲取 token 到使用 token 訪問接口。這實際上是標準的 OAuth2.0 機制下訪問 API 的流程。這裏介紹一下 OAuth 裏外相關的概念,更深刻的理解 token 的做用。

SSO (Single sign-on)

一般公司內部會有很是多的平臺供你們使用,好比人力資源,代碼管理,日誌監控,預算申請等等。若是每個平臺都實現本身的用戶體系的話無疑是巨大的浪費,因此公司內部會有一套 公用的用戶體系,用戶只要登錄以後,就可以 訪問全部的系統。這就是 單點登陸

SSO 是一類 解決方案 的統稱,而在具體的實施方面,咱們有兩種策略可供選擇:

  • SAML 2.0

  • OAuth 2.0

接下來咱們區別這 兩種受權方式 有什麼不一樣。可是在描述 不一樣的策略 以前,咱們先敘述幾個 共有的特性,而且至關重要的概念。

Authentication VS Authorisation

  • Authentication: 身份鑑別,如下簡稱 認證

  • Authorisation: 資源訪問 受權

認證 的做用在於 承認 你可以訪問系統,用於 鑑別訪問者 是不是 合法用戶;而 受權 用於決定你有訪問 哪些資源的權限

大多數人不會區分這二者的區別,由於站在用戶的立場上。而做爲系統的設計者來講,這二者是有差異的,這是不一樣的兩個工做職責。咱們能夠只須要 認證功能,而不須要 受權功能,甚至不須要本身實現 認證功能。而藉助 Google 的認證系統,即用戶能夠用 Google 的帳號進行登錄。

Authorization Server/Identity Provider(IdP)

把負責 認證的服務 稱爲 AuthorizationServer 或者 IdentityProvider,如下簡稱 IDP

Service Provider(SP)/Resource Server

把負責 提供資源API 調用)的服務稱爲 ResourceServer 或者 ServiceProvider,如下簡稱 SP

SAML 2.0

下圖是 SAML2.0 的流程圖,看圖說話:

  1. 未登錄 的用戶 打開瀏覽器 訪問你的網站(SP),網站 提供服務 可是並 不負責用戶認證

  2. 因而 SPIDP 發送了一個 SAML 認證請求,同時 SP用戶瀏覽器 重定向到 IDP

  3. IDP 在驗證完來自 SP請求無誤 以後,在瀏覽器中呈現 登錄表單 讓用戶填寫 用戶名密碼 進行登錄。

  4. 一旦用戶登錄成功, IDP 會生成一個包含 用戶信息用戶名 或者 密碼)的 SAML tokenSAML token 又稱爲 SAML Assertion,本質上是 XML 節點)。IDPSP 返回 token,而且將 用戶重定向SP (token 的返回是在 重定向步驟 中實現的,下面會詳細說明)。

  5. SP 對拿到的 token 進行驗證,並從中解析出 用戶信息,例如 用戶是誰 以及 用戶的權限 有哪些。此時就可以根據這些信息容許用戶訪問咱們網站的內容。

當用戶在 IDP 登錄成功以後,IDP 須要將用戶 再次重定向SP 站點,這一步一般有兩個辦法:

  • HTTP 重定向:這並不推薦,由於 重定向URL 長度 有限制,沒法攜帶更長的信息,好比 SAML Token

  • HTTP POST 請求:這個是更常規的作法,當用戶登錄完畢以後渲染出一個表單,用戶點擊後向 SP 提交 POST 請求。又或者可使用 JavaScriptSP 發出一個 POST 請求。

若是你的應用是基於 Web,那麼以上的方案沒有任何問題。但若是你開發的是一個 iOS 或者 Android 的手機應用,那麼問題就來了:

  1. 用戶在 iPhone 上打開應用,此時用戶須要經過 IDP 進行認證。

  2. 應用跳轉至 Safari 瀏覽器,在登錄認證完畢以後,須要經過 HTTP POST 的形式將 token 返回至 手機應用

雖然 POSTurl 能夠 拉起應用,可是 手機應用 沒法解析 POST 的內容,咱們也就沒法讀取 SAML Token

固然仍是有辦法的,好比在 IDP 受權階段 不跳轉至系統的 Safari 瀏覽器,在 內嵌Webview 中解決,在千方百計從 Webview 中提取 token,或者利用 代理服務器

不管如何,SAML 2.0不適用 於當下 跨平臺 的場景,這也許與它產生的年代也有關係,它誕生於 2005 年,在那個時刻 HTTP POST 確實是最好的選擇方案。

OAuth 2.0

咱們先簡單瞭解 SSO 下的 OAuth2.0 的流程。

  1. 用戶經過 客戶端(能夠是 瀏覽器 也能夠是 手機應用)想要訪問 SP 上的資源,可是 SP 告訴用戶須要進行 認證,將用戶 重定向IDP

  2. IDP用戶 詢問 SP 是否能夠訪問 用戶信息。若是用戶贊成,IDP客戶端 返回 authorization code

  3. 客戶端拿到 authorization codeIDP 交換 access token,並拿着 access tokenSP 請求資源。

  4. SP 接受到請求以後,拿着附帶的 tokenIDP 驗證 用戶的身份。確認身份無誤後,SP客戶端 發放相關資源。

那麼 OAuth 是如何避免 SAML 流程下 沒法解析 POST 內容的信息的呢?

  • 一方面是用戶從 IDP 返回 客戶端 的方式,也是經過 URL 重定向,這裏的 URL 容許 自定義 schema,因此即便在 手機 上也能 拉起應用

  • 另外一方面由於 IDP客戶端 傳遞的是 authorization code,而不是 XML 信息,因此 code 能夠很輕易的附着在 重定向 URL 上進行傳遞。

但以上的 SSO 流程體現不出 OAuth 的本意。OAuth 的本意是 一個應用 容許 另外一個應用用戶受權 的狀況下 訪問本身的數據

OAuth 的設計本意更傾向於 受權而非認證(固然受權用戶信息就間接實現了認證),雖然 GoogleOAuth 2.0 API 同時支持 受權認證。因此你在使用 Facebook 或者 Gmail 帳號登錄第三方站點時,會出現 受權對話框,告訴你 第三方站點 能夠訪問你的哪些信息,須要徵得你的贊成。

在上面 SSOOAuth 流程中涉及三方角色: SP, IDP 以及 Client。但在實際工做中 Client 能夠是不存在的,例如你編寫了一個 後端程序 定時的經過 Google APIYoutube 拉取最新的節目數據,那麼你的 後端程序 須要獲得 YoutubeOAuth 受權 便可。

OAuth VS OpenId

若是你有留心的話,你會在某些站點看到容許以 OpenID 的方式登錄,其實也就是以 Facebook 帳號或者 Google 帳號登錄站點:

OpenIDOAuth 很像。但本質上來講它們是大相徑庭的兩個東西:

  • OpenID: 只用於 身份認證Authentication),容許你以 同一個帳戶多個網站登錄。它僅僅是爲你的 合法身份 背書,當你以 Facebook 帳號登錄某個站點以後,該站點 無權訪問 你的在 Facebook 上的 數據

  • OAuth: 用於 受權Authorisation),容許 被受權方 訪問 受權方用戶數據

Refresh Token

如今能夠回答上面的問題了,爲何咱們須要 refresh token

這樣的處理是爲了 職責的分離

  • refresh token: 負責 身份認證

  • access token: 負責 請求資源

雖然 refresh tokenaccess token 都由 IDP 發出,可是 access token 還要和 SP 進行 數據交換,若是 公用的話 這樣就會有 身份泄露 的可能。而且 IDPSP 多是 徹底不一樣服務提供 的。而在上文,咱們之因此沒有這樣的顧慮是由於 IDPSP 都是 Google

JWT

初步認識

本質上來講 JWT 也是 token,正如咱們在上文提到的,它是 訪問資源憑證

Google 的一些 API 諸如 Prediction API 或者 Google Cloud Storage,是不須要 訪問 用戶的 我的數據 的。於是不須要通過 用戶的受權 這一步驟,應用程序能夠直接訪問。就像上面 OAuth 中沒有 Client 沒有參與的流程相似。這就要藉助 JWT 完成訪問了, 具體流程以下:

  1. 首先須要在 Google API 上建立一個服務帳號(service account)。

  2. 獲取 服務帳號認證信息credential),包括 郵箱地址client ID,以及一對 公鑰/私鑰

  3. 使用 Client ID私鑰 創一個 簽名JWT,而後將這個 JWT 發送給 Google 交換 access token

  4. Google 返回 access token

  5. 程序經過 access token 訪問 API

甚至你能夠不須要向 Google 索要 access token,而是攜帶 JWT 做爲 HTTP header 裏的 bearer token 直接訪問 API 也是能夠的。這纔是 JWT 的最大魅力。

理性認識

JWT 顧名思義,它是 JSON 結構的 token,由三部分組成:

  • header

  • payload

  • signature

header

header 用於描述 元信息,例如產生 signature 的算法:

{    
    "typ": "JWT",
    "alg": "HS256"
}
複製代碼

其中 alg 關鍵字就指定了使用哪種 哈希算法 來建立 signature

payload

payload 用於攜帶你但願 向服務端傳遞 的信息。你既能夠往裏添加 官方字段,例如:iss(Issuer), sub(Subject), exp(Expirationtime),也能夠塞入 自定義的字段,好比 userId:

{
    "userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
複製代碼

signature

signature 譯爲 簽名,建立簽名要分如下幾個步驟:

  1. 接口服務端 拿到 密鑰,假設爲 secret

  2. header 進行 base64 編碼,假設結果爲 headerStr

  3. payload 進行 base64 編碼,假設結果爲 payloadStr

  4. headerStrpayloadStr. 字符 拼裝起來成爲字符 data

  5. datasecret 做爲參數,使用 哈希算法 計算出 簽名

若是上述描述還不直觀,用 僞代碼 表示就是:

// Signature algorithm
data = base64urlEncode( header ) + 「.」 + base64urlEncode( payload )
signature = Hash( data, secret );
複製代碼

假設咱們的原始 JSON 結構是這樣的:

// Header
{
    "typ": "JWT",
    "alg": "HS256"
}

// Payload
{
    "userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
複製代碼

若是 密鑰 是字符串 secret 的話,那麼最終 JWT 的結果就是這樣的:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM
複製代碼

能夠在 jwt.io驗證 這個結果。

JWT究竟帶來了什麼

確保數據完整性

JWT 的目的不是爲了 隱藏 或者 保密數據,而是爲了確保 數據 確實來自被 受權的人 建立的,以防止 中途篡改

回想一下,當你拿到 JWT 時候,你徹底能夠在沒有 secret 的狀況下解碼出 headerpayload,由於 headerpayload 只是通過了 base64 編碼(encode)而已,編碼的目的在於 利於數據結構的傳輸

雖然建立 signature 的過程近似於 加密 (encrypt),但本質實際上是一種 簽名 (sign) 的行爲,用於保證 數據的完整性,實際上也而且並 沒有加密任何數據

用於接口調用

接下來在 API 調用中就能夠附上 JWT(一般是在 HTTP Header 中)。又由於 SP 會與程序 共享 一個 secret,因此 程序 能夠經過 header 提供的相同的 hash 算法來 驗證簽名 是否正確,從而判斷應用是否有權力調用 API

有狀態的對話Session

由於 HTTP無狀態 的,因此 客戶端服務端 須要解決的問題是,如何讓它們之間的對話變得有狀態。例如只有是 登錄狀態用戶 纔有權限調用某些接口,那麼在 用戶登錄 以後,須要記住該用戶是 已經登錄 的狀態。常見的方法是使用 session 機制。

常見的 session 模型是這樣工做的:

  1. 用戶在瀏覽器 登錄 以後,服務端爲用戶生成 惟一session id,存儲在 服務端存儲服務(例如 MySQL, Redis)中。

  2. session id 也同時 返回給瀏覽器,以 SESSION_IDKEY 存儲在瀏覽器的 cookie 中。

  3. 若是用戶再次訪問該網站,cookie 裏的 SESSION_ID 會隨着 請求 一同發往 服務端

  4. 服務端經過判斷 SESSION_ID 是否已經在 Redis 中判斷用戶是否處於 登錄狀態

相信你已經察覺了,理論上來講,JWT 機制能夠取代 session 機制。用戶不須要提早進行登錄,後端也不須要 Redis 記錄用戶的登錄信息。客戶端的本地保存一份合法的 JWT,當用戶須要調用接口時,附帶上該合法的 JWT,每一次調用接口,後端都使用請求中附帶的 JWT 作一次 合法性的驗證。這樣也間接達到了 認證用戶 的目的。

然而 JWT 真的能取代 session 機制嗎?這麼作有哪些好處和壞處?這些問題將留在下一篇再討論。


歡迎關注技術公衆號: 零壹技術棧

零壹技術棧

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索