API:認證、授權、憑證
以往講使用 spring security 等具體技術的資料已經很多了,筆者這篇文章不打算寫框架和代碼的具體實現,而是會討論認證和授權的區別。然后會介紹一些被業界廣泛采用的技術,最后會聊聊:怎么為 API 構建選擇合適的認證方式?
在一些互聯網公司的面試中,面試官往往會問這樣一個問題:“如果禁用瀏覽器 cookie,如何實現用戶追蹤和認證?”
遺憾的是:依然有大量候選人答非所問,無法搞清楚 cookie 和 session 之間的區別。
而在工作中也有讓人驚訝的真實案例:把 user ID 存儲到 local storage 中當做 token 使用,原因是他們聲稱棄用了 cookie 這種落后的東西;一個移動端項目,服務器給出的 API 中需要客戶端模擬一個 cookie,從而像瀏覽器中 ajax 那樣消費 API。
互聯網是基于 HTTP 協議構建的,而 HTTP 協議因為簡單流行開來,但是 HTTP 協議是無狀態(通信層面上虛電路比數據報昂貴太多)的。為此人們為了追蹤用戶想出了各種辦法,包括 :cookie/session 機制、token、flash 跨瀏覽器 cookie 甚至瀏覽器指紋等。
把用戶身份藏在每一個地方(瀏覽器指紋技術甚至不需要存儲介質)
認證、授權、憑證
首先,認證和授權是兩個不同的概念,為了讓我們的 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
你一定用過這種方式,但不一定知道它是什么。
在不久之前,當你訪問一臺家用路由器的管理界面,往往會看到一個瀏覽器彈出表單,要求你輸入用戶密碼。
在這背后,當用戶輸入完用戶名密碼后,瀏覽器幫你做了一個非常簡單的操作:
- 組合用戶名和密碼然后 Base64 編碼。
- 給編碼后的字符串添加 Basic 前綴,然后設置名稱為 Authorization 的 header 頭部。
API 也可以非常簡單的提供 HTTP Basic Authentication 認證方式,那么客戶端可以很簡單通過 Base64 傳輸用戶名和密碼即可:。
- 將用戶名和密碼使用冒號連接,例如 :username:abc123456。
- 為了防止用戶名或者密碼中存在超出 ASCII 碼范圍的字符,推薦使用UTF-8編碼。
- 將上面的字符串使用 Base 64 編碼,例如:dXNlcm5hbWU6YWJjMTIzNDU2。
- 在 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 時有具體不同的實現。
HMAC 在作為網絡通信的認證設計中作為憑證生成算法使用,避免了口令等敏感信息在網絡中傳輸。
基本過程如下:
- 客戶端需要在認證服務器中預先設置 access key(AK 或叫 app ID) 和 secure key(SK)。
- 在調用 API 時,客戶端需要對參數和 access key 進行自然排序后并使用 secure key 進行簽名生成一個額外的參數 digest。
- 服務器根據預先設置的 secure key 進行同樣的摘要計算,并要求結果完全一致。
- 注意 secure key 不能在網絡中傳輸,以及在不受信任的位置存放(瀏覽器等)。
為了讓每一次請求的簽名變得獨一無二,從而實現重放攻擊,我們需要在簽名時放入一些干擾信息。
在業界標準中有兩種典型的做法,質疑/應答算法(OCRA: OATH Challenge-Response Algorithm)、基于時間的一次性密碼算法(TOTP:Time-based One-time Password Algorithm)。
1. 質疑/應答算法
質疑/應答算法需要客戶端先請求一次服務器,獲得一個 401 未認證的返回,并得到一個隨機字符串(nonce)。
將 nonce 附加到按照上面說到的方法進行 HMAC 簽名,服務器使用預先分配的 nonce 同樣進行簽名校驗,這個 nonce 在服務器只會被使用一次,因此可以提供唯一的摘要。
2. 基于時間的一次性密碼認證
為了避免額外的請求來獲取 nonce,還有一種算法是使用時間戳,并且通過同步時間的方式協商到一致,在一定的時間窗口內有效(1分鐘左右)。
這里的只是利用時間戳作為驗證的時間窗口,并不能嚴格的算作基于時間的一次性密碼算法。
標準的基于時間的一次性密碼算法在兩步驗證中被大量使用,例如:Google 身份驗證器不需要網絡通信也能實現驗證(但依賴準確的授時服務)。
原理是:客戶端服務器共享密鑰然后根據時間窗口能通過 HMAC 算法計算出一個相同的驗證碼。
TOTP 基本原理和常見廠商
OAuth2 和 Open ID
OAuth(開放授權)是一個開放標準,允許用戶授權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方網站或分享他們數據的所有內容。
OAuth 是一個授權標準,而不是認證標準。提供資源的服務器不需要知道確切的用戶身份(session),只需要驗證授權服務器授予的權限(token)即可。
上圖只是 OAuth 的一個簡化流程,OAuth 的基本思路就是通過授權服務器獲取 access token 和 refresh token(refresh token 用于重新刷新access token),然后通過 access token 從資源服務器獲取數據 。
在特定的場景下還有下面幾種模式:
- 授權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
如果需要獲取用戶的認證信息,OAuth 本身沒有定義這部分內容,如果需要識別用戶信息,則需要借助另外的認證層,例如: OpenID Connect。
1. 驗證 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 的部分解釋。
2. 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,客戶端和授權服務器之前始終存在安全的認證。
3. 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)。
簡而言之,一個基本的JWT令牌為一段點分3段式結構。
生成JWT 令牌的流程為:
- header json 的 base64 編碼為令牌第一部分。
- payload json 的 base64 編碼為令牌第二部分。
- 拼裝第一、第二部分編碼后的 json 以及 secret 進行簽名的令牌的第三部分。
因此,只需要簽名的 secret key 就能校驗 JWT 令牌,如果在消息體中加入用戶 ID、過期信息就可以實現驗證令牌是否有效、過期了,無需從數據庫/緩存中讀取信息。因為使用了加密算法,所以第一、二部分即使被修改(包括過期信息)也無法通過驗證。
JWT 優點是:不僅可以作為 token 使用,同時也可以承載一些必要信息,省去多次查詢。
注意:
- JWT token 的第一、二部分只是 base64 編碼,肉眼不可讀,不應當存放敏感信息。
- JWT token 的自包含特性,導致了無法被撤回。
- 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 也可以完成任務。
在一些遺留或者不是標準的認證實現的項目中,我們依然可以看到這些做法,快速地實現認證。
- 使用 cookie,例如 web 項目中 ajax 的方式。
- 使用 session ID 或 hash 作為 token,但將 token 放入 header 中傳遞。
- 將生成的 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。
從一個宏觀的角度看待他們的關系,對我們技術選型非常有幫助。
術語表
- Browser fingerprinting 通過查詢瀏覽器的代理字符串,屏幕色深,語言等,然后這些值通過散列函數傳遞產生指紋,不需要通過 Cookie 就可以識別瀏覽器。
- MAC(Message authentication code) 在密碼學中,訊息鑒別碼,是經過特定算法后產生的一小段資訊,檢查某段訊息的完整性。
- HOTP(HMAC-based One-time Password algorithm)基于散列消息驗證碼的一次性密碼算法。
- Two-step verification 是一種認證方法,使用兩種不同的元素,合并在一起,來確認使用者的身份,是多因素驗證中的一個特例。
- OTP (One time password )一次性密碼,例如注冊郵件和短信中的認證碼。
參考文章
https://swagger.io/docs/specification/authentication/basic-authentication/
[HMAC: Keyed-Hashing for Message Authentication]( “https://www.ietf.org/rfc/rfc2104.txt “)
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洞見
本文由@ThoughtWorks林寧 原創發布于人人都是產品經理,未經允許,禁止轉載。
題圖來自Unsplash, 基于CC0協議
test
??