細說API - 認證、授權和憑證

ThoughtWorks發表於2019-03-27

在一些網際網路公司的面試中,面試官往往會問這樣一個問題:

“如果禁用瀏覽器 cookie,如何實現使用者追蹤和認證?”

遺憾的是依然有大量候選人答非所問,無法搞清楚 cookie 和 session 之間的區別。而在工作中也有讓人驚訝的真實案例:把 user ID 儲存到 local storage 中當做 token 使用,原因是他們聲稱棄用了 cookie 這種落後的東西;一個移動端專案,伺服器給出的 API 中需要客戶端模擬一個 cookie,從而像瀏覽器中 ajax 那樣消費 API。

網際網路是基於 HTTP 協議構建的,而 HTTP 協議因為簡單流行開來,但是 HTTP 協議是無狀態(通訊層面上虛電路比資料包昂貴太多)的,為此人們為了追蹤使用者想出了各種辦法,包括 cookie/session 機制、token、flash 跨瀏覽器 cookie 甚至瀏覽器指紋等。

細說API - 認證、授權和憑證

把使用者身份藏在每一個地方(瀏覽器指紋技術甚至不需要儲存介質)

講使用 spring security 等具體技術的資料已經很多了,這篇文章不打算寫框架和程式碼的具體實現。我們會討論認證和授權的區別,然後會介紹一些被業界廣泛採用的技術,最後會聊聊怎麼為 API 構建選擇合適的認證方式。

認證、授權、憑證

首先,認證和授權是兩個不同的概念,為了讓我們的 API 更加安全和具有清晰的設計,理解認證和授權的不同就非常有必要了,它們在英文中也是不同的單詞。

細說API - 認證、授權和憑證

認證是 authentication,指的是當前使用者的身份,當使用者登陸過後系統便能追蹤到他的身份做出符合相應業務邏輯的操作。即使使用者沒有登入,大多數系統也會追蹤他的身份,只是當做來賓或者匿名使用者來處理。認證技術解決的是 “我是誰?”的問題。

授權則不同,授權是 authorization,指的是什麼樣的身份被允許訪問某些資源,在獲取到使用者身份後繼續檢查使用者的許可權。單一的系統授權往往是伴隨認證來完成的,但是在開放 API 的多系統結構下,授權可以由不同的系統來完成,例如 OAuth。授權技術是解決“我能做什麼?”的問題。

實現認證和授權的基礎是需要一種媒介(credentials)來標記訪問者的身份或權利,在現實生活中每個人都需要一張身份證才能訪問自己的銀行賬戶、結婚和辦理養老保險等,這就是認證的憑證;在古代軍事活動中,皇帝會給出戰的將軍頒發兵符,下級將領不關心持有兵符的人,只需要執行兵符對應的命令即可。在網際網路世界中,伺服器為每一個訪問者頒發 session ID 存放到 cookie,這就是一種憑證技術。數字憑證還表現在方方面面,SSH 登入的密匙、JWT 令牌、一次性密碼等。

使用者賬戶也不一定是存放在資料庫中的一張表,在一些企業 IT 系統中,對賬戶管理和許可權有了更多的要求。所以賬戶技術 (accounting)可以幫助我們使用不同的方式管理使用者賬戶,同時具有不同系統之間共享賬戶的能力。例如微軟的活動目錄(AD),以及簡單目錄訪問協議(LDAP),甚至區塊鏈技術。

還有一個重要的概念是訪問控制策略(AC)。如果我們需要把資源的許可權劃分到一個很細的粒度,就不得不考慮使用者以何種身份來訪問受限的資源,選擇基於訪問控制列表(ACL)還是基於使用者角色的訪問控制(RBAC)或者其他訪問控制策略。

在流行的技術和框架中,這些概念都無法孤立的被實現,因此在現實中使用這些技術時,大家往往為一個 OAuth2 是認證還是授權這種概念爭論不休。為了容易理解,我在文末附上了一份常見技術和概念的術語表。下面我會介紹在API開發中常常使用的幾種認證和授權技術:HTTP Basic AUthentication、HAMC、OAuth2,以及憑證技術JWT token。

HTTP Basic Authentication

你一定用過這種方式,但不一定知道它是什麼,在不久之前,當你訪問一臺家用路由器的管理介面,往往會看到一個瀏覽器彈出表單,要求你輸入使用者密碼。

細說API - 認證、授權和憑證

在這背後,當使用者輸入完使用者名稱密碼後,瀏覽器幫你做了一個非常簡單的操作:

  1. 組合使用者名稱和密碼然後 Base64 編碼

  2. 給編碼後的字串新增 Basic 字首,然後設定名稱為 Authorization 的 header 頭部

細說API - 認證、授權和憑證

API 也可以非常簡單的提供 HTTP Basic Authentication 認證方式,那麼客戶端可以很簡單通過 Base64 傳輸使用者名稱和密碼即可:

  1. 將使用者名稱和密碼使用冒號連線,例如 username:abc123456
  2. 為了防止使用者名稱或者密碼中存在超出 ASCII 碼範圍的字元,推薦使用UTF-8編碼
  3. 將上面的字串使用 Base 64 編碼,例如 dXNlcm5hbWU6YWJjMTIzNDU2
  4. 在 HTTP 請求頭中加入 “Basic + 編碼後的字串”,即:Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

這種方式實現起來非常簡單,在大量場景下被採用。當然缺點也很明顯,Base64 只能稱為編碼,而不是加密 (實際上無需配置密匙的客戶端並沒有任何可靠地加密方式,我們都依賴 TSL 協議)。這種方式的致命弱點是編碼後的密碼如果明文傳輸則容易在網路傳輸中洩露,在密碼不會過期的情況下,密碼一旦洩露,只能通過修改密碼的方式。

HMAC(AK/SK)認證

在我們對接一些 PASS 平臺和支付平臺時,會要求我們預先生成一個 access key(AK) 和 secure key(SK),然後通過簽名的方式完成認證請求,這種方式可以避免傳輸 secure key,且大多數情況下簽名只允許使用一次,避免了重放攻擊。

這種基於 AK/SK 的認證方式主要是利用雜湊的訊息認證碼 (Hash-based MessageAuthentication Code) 來實現的,因此有很多地方叫 HMAC 認證,實際上不是非常準確。HMAC 只是利用帶有 key 值的雜湊演算法生成訊息摘要,在設計 API 時有具體不同的實現。

細說API - 認證、授權和憑證

HMAC 在作為網路通訊的認證設計中作為憑證生成演算法使用,避免了口令等敏感資訊在網路中傳輸。基本過程如下:

  1. 客戶端需要在認證伺服器中預先設定 access key(AK 或叫 app ID) 和 secure key(SK)
  2. 在呼叫 API 時,客戶端需要對引數和 access key 進行自然排序後並使用 secure key 進行簽名生成一個額外的引數 digest
  3. 伺服器根據預先設定的 secure key 進行同樣的摘要計算,並要求結果完全一致
  4. 注意 secure key 不能在網路中傳輸,以及在不受信任的位置存放(瀏覽器等)

為了讓每一次請求的簽名變得獨一無二,從而實現重放攻擊,我們需要在簽名時放入一些干擾資訊。

在業界標準中有兩種典型的做法,質疑/應答演算法(OCRA: OATH Challenge-Response Algorithm)、基於時間的一次性密碼演算法(TOTP:Time-based One-time Password Algorithm)。

質疑/應答演算法

質疑/應答演算法需要客戶端先請求一次伺服器,獲得一個 401 未認證的返回,並得到一個隨機字串(nonce)。將 nonce 附加到按照上面說到的方法進行 HMAC 簽名,伺服器使用預先分配的 nonce 同樣進行簽名校驗,這個 nonce 在伺服器只會被使用一次,因此可以提供唯一的摘要。

細說API - 認證、授權和憑證

基於時間的一次性密碼認證

為了避免額外的請求來獲取 nonce,還有一種演算法是使用時間戳,並且通過同步時間的方式協商到一致,在一定的時間視窗內有效(1分鐘左右)。

細說API - 認證、授權和憑證

這裡的只是利用時間戳作為驗證的時間視窗,並不能嚴格的算作基於時間的一次性密碼演算法。標準的基於時間的一次性密碼演算法在兩步驗證中被大量使用,例如 Google 身份驗證器不需要網路通訊也能實現驗證(但依賴準確的授時服務)。原理是客戶端伺服器共享金鑰然後根據時間視窗能通過 HMAC 演算法計算出一個相同的驗證碼。

細說API - 認證、授權和憑證

TOTP 基本原理和常見廠商

OAuth2 和 Open ID

OAuth(開放授權)是一個開放標準,允許使用者授權第三方網站訪問他們儲存在另外的服務提供者上的資訊,而不需要將使用者名稱和密碼提供給第三方網站或分享他們資料的所有內容。

OAuth 是一個授權標準,而不是認證標準。提供資源的伺服器不需要知道確切的使用者身份(session),只需要驗證授權伺服器授予的許可權(token)即可。

細說API - 認證、授權和憑證

上圖只是 OAuth 的一個簡化流程,OAuth 的基本思路就是通過授權伺服器獲取 access token 和 refresh token(refresh token 用於重新重新整理access token),然後通過 access token 從資源伺服器獲取資料 。在特定的場景下還有下面幾種模式:

  1. 授權碼模式(authorization code)
  2. 簡化模式(implicit)
  3. 密碼模式(resource owner password credentials)
  4. 客戶端模式(client credentials)

如果需要獲取使用者的認證資訊,OAuth 本身沒有定義這部分內容,如果需要識別使用者資訊,則需要藉助另外的認證層,例如 OpenID Connect。

驗證 access token

在一些介紹OAuth 的部落格中很少講到資源伺服器是怎麼驗證 access token 的。OAuth core 標準並沒有定義這部分,不過在 OAuth 其他標準檔案中提到兩種驗證 access token的方式。

  1. 在完成授權流程後,資源伺服器可以使用 OAuth 伺服器提供的 Introspection 介面來驗證access token,OAuth伺服器會返回 access token 的狀態以及過期時間。在OAuth標準中驗證 token 的術語是 Introspection。同時也需要注意 access token 是使用者和資源伺服器之間的憑證,不是資源伺服器和授權伺服器之間的憑證。資源伺服器和授權伺服器之間應該使用額外的認證(例如 Basic 認證)。
  2. 使用 JWT 驗證。授權伺服器使用私鑰簽發 JWT 形式的 access token,資源伺服器需要使用預先配置的公鑰校驗 JWT token,並得到 token 狀態和一些被包含在 access token 中資訊。因此在 JWT 的方案下,資源伺服器和授權伺服器不再需要通訊,在一些場景下帶來巨大的優勢。同時 JWT 也有一些弱點,我會在JWT 的部分解釋。

refresh token 和 access token

幾乎所有人剛開始瞭解 OAuth 時都有一個一疑問,為什麼已經有了 access token 還需要 refresh token 呢?

授權伺服器會在第一次授權請求時一起返回 access token 和refresh token,在後面重新整理 access token 時只需要 refresh token。 access token 和 refresh token 的設計意圖是不一樣的,access token 被設計用來客戶端和資源伺服器之間互動,而 refresh token 是被設計用來客戶端和授權伺服器之間互動。

某些授權模式下 access token 需要暴露給瀏覽器,充當一個資源伺服器和瀏覽器之間的臨時會話,瀏覽器和資源伺服器之間不存在簽名機制,access token 成為唯一憑證,因此 access token 的過期時間(TTL)應該儘量短,從而避免使用者的 access token 被嗅探攻擊。

由於要求 access token 時間很短,refresh token 可以幫助使用者維護一個較長時間的狀態,避免頻繁重新授權。大家會覺得讓 access token 保持一個長的過期時間不就可以了嗎?實際上 refresh token 和 access token 的不同之處在於即使 refresh token 被截獲,系統依然是安全的,客戶端拿著 refresh token 去獲取 access token 時同時需要預先配置的 secure key,客戶端和授權伺服器之前始終存在安全的認證。

OAuth、Open ID、OpenID Connect

認證方面的術語實在太多,我在搭建自己的認證伺服器或接入第三方認證平臺時,有時候到完成開發工作的最後一刻都無法理解這些術語。

OAuth 負責解決分散式系統之間的授權問題,即使有時候客戶端和資源伺服器或者認證伺服器存在同一臺機器上。OAuth 沒有解決認證的問題,但提供了良好的設計利於和現有的認證系統對接。

Open ID 解決的問題是分散式系統之間身份認證問題,使用Open ID token 能在多個系統之間驗證使用者,以及返回使用者資訊,可以獨立使用,與 OAuth 沒有關聯。

OpenID Connect 解決的是在 OAuth 這套體系下的使用者認證問題,實現的基本原理是將使用者的認證資訊(ID token)當做資源處理。在 OAuth 框架下完成授權後,再通過 access token 獲取使用者的身份。

這三個概念之間的關係有點難以理解,用現實場景來說,如果系統中需要一套獨立的認證系統,並不需要多系統之間的授權可以直接採用 Open ID。如果使用了 OAuth 作為授權標準,可以再通過 OpenID Connect 來完成使用者的認證。

JWT

在 OAuth 等分散式的認證、授權體系下,對憑證技術有了更多的要求,比如包含使用者 ID、過期等資訊,不需要再外部儲存中關聯。因此業界對 token 做了進一步優化,設計了一種自包含令牌,令牌簽發後無需從伺服器儲存中檢查是否合法,通過解析令牌就能獲取令牌的過期、有效等資訊,這就是JWT (JSON Web Token)。

JWT 是一種包含令牌(self-contained token),或者叫值令牌 (value token),我們以前使用關聯到 session 上的 hash 值被叫做引用令牌(reference token)。

細說API - 認證、授權和憑證

簡而言之,一個基本的JWT令牌為一段點分3段式結構。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

生成JWT 令牌的流程為

細說API - 認證、授權和憑證

  1. header json 的 base64 編碼為令牌第一部分
  2. payload json 的 base64 編碼為令牌第二部分
  3. 拼裝第一、第二部分編碼後的 json 以及 secret 進行簽名的令牌的第三部分

因此只需要簽名的 secret key 就能校驗 JWT 令牌,如果在訊息體中加入使用者 ID、過期資訊就可以實現驗證令牌是否有效、過期了,無需從資料庫/快取中讀取資訊。因為使用了加密演算法,所以第一、二部分即使被修改(包括過期資訊)也無法通過驗證。JWT 優點是不僅可以作為 token 使用,同時也可以承載一些必要資訊,省去多次查詢。

注意:

  1. JWT token 的第一、二部分只是 base64 編碼,肉眼不可讀,不應當存放敏感資訊
  2. JWT token 的自包含特性,導致了無法被撤回
  3. JWT 的簽名演算法可以自己擬定,為了便於除錯,本地環境可以使用對稱加密演算法,生產環境建議使用非對稱加密演算法

JWT token 在微服務的系統中優勢特別突出。多層呼叫的 API 中可以直接傳遞 JWT token,利用自包含的能力,可以減少使用者資訊查詢次數;更重要的是,使用非對稱的加密方式可以通過在系統中分發密匙的方式驗證 JWT token。

當然 OAuth 對 access token 等憑證所選用的技術並沒有做出限制,OAuth 並不強制使用 JWT,在使用 JWT 自包含特性的優勢時,必須考慮到 JWT 撤回困難的問題。在一些對撤回 token 要求很高的專案中不適合使用JWT,即使採用了一些方案實現(whitelist 和 blacklist)也違背了設計 JWT 的初衷。

Cookie 、Token in Cookie、Session Token 依然被使用

在構建 API 時,開發者會發現我們的認證方式和網頁應用有一些不同,除了像 ajax 這種典型的 web 技術外,如果我們希望 API 是無狀態的,不推薦使用 Cookie。

使用 Cookie 的本質是使用者第一次訪問時伺服器會分配一個 Session ID,後面的請求中客戶端都會帶上這個 ID 作為當前使用者的標誌,因為 HTTP 本身是無狀態的,Cookie 屬於一種內建於瀏覽器中實現狀態的方式。如果我們的 API 是用來給客戶端使用的,強行要求 API 的呼叫者管理Cookie 也可以完成任務。

在一些遺留或者不是標準的認證實現的專案中,我們依然可以看到這些做法,快速地實現認證。

  1. 使用 cookie,例如 web 專案中 ajax 的方式
  2. 使用 session ID 或 hash 作為 token,但將 token 放入 header 中傳遞
  3. 將生成的 token (可能是JWT)放入 cookie 傳遞,利用 HTTPonly 和 Secure 標籤保護 token

選擇合適的認證方式

隨著微服務的發展,API 的設計不僅僅是面向 WEB 或者 Mobile APP,還有BFF(Backend for Frontend)和 Domain API 的認證,以及第三方服務的整合。

客戶端到伺服器之間認證和伺服器到伺服器之間認證是不同的。

我們把終端使用者(Human)參與的通訊,叫做 Human-to-machine (H2M),伺服器與伺服器之間的通訊叫做 Machine-to-machine (M2M)。

H2M 的通訊需要更高的安全性,M2M 的通訊天然比 H2M 安全,因此更多的強調效能,在不同的場合下選擇合適的認證技術就顯得特別重要。例如 HTTP Basic Authentication 用來作為 H2M 認證顯得有些落後,但是在 M2M 中被大量使用。

另外值得一提的是,H2M 這種通訊方式下,客戶端不受控制,由於無法自主分發密匙,認證通訊的安全高度依賴 HTTPS。

從一個巨集觀的角度看待他們的關係,對我們技術選型非常有幫助。

細說API - 認證、授權和憑證

術語表

  1. Browser fingerprinting 通過查詢瀏覽器的代理字串,螢幕色深,語言等,然後這些值通過雜湊函式傳遞產生指紋,不需要通過 Cookie 就可以識別瀏覽器
  2. MAC(Message authentication code) 在密碼學中,訊息鑑別碼,是經過特定演算法後產生的一小段資訊,檢查某段訊息的完整性
  3. HOTP(HMAC-based One-time Password algorithm)基於雜湊訊息驗證碼的一次性密碼演算法
  4. Two-step verification 是一種認證方法,使用兩種不同的元素,合併在一起,來確認使用者的身份,是多因素驗證中的一個特例
  5. OTP (One time password )一次性密碼,例如註冊郵件和簡訊中的認證碼

參考文章

swagger.io/docs/specif…

[HMAC: Keyed-Hashing for Message Authentication]( "www.ietf.org/rfc/rfc2104… ")

HOTP: An HMAC-Based One-Time Password Algorithm

OCRA: OATH Challenge-Response Algorithm

The OAuth 2.0 Authorization Framework

JSON Web Token (JWT)

OAuth 2.0

Internet-Draft Archive for OAuth


文/ThoughtWorks林寧

更多精彩洞見,請關注微信公眾號:ThoughtWorks洞見

相關文章