API:認證、授權(quán)、憑證
以往講使用 spring security 等具體技術(shù)的資料已經(jīng)很多了,筆者這篇文章不打算寫框架和代碼的具體實現(xiàn),而是會討論認證和授權(quán)的區(qū)別。然后會介紹一些被業(yè)界廣泛采用的技術(shù),最后會聊聊:怎么為 API 構(gòu)建選擇合適的認證方式?
在一些互聯(lián)網(wǎng)公司的面試中,面試官往往會問這樣一個問題:“如果禁用瀏覽器 cookie,如何實現(xiàn)用戶追蹤和認證?”
遺憾的是:依然有大量候選人答非所問,無法搞清楚 cookie 和 session 之間的區(qū)別。
而在工作中也有讓人驚訝的真實案例:把 user ID 存儲到 local storage 中當做 token 使用,原因是他們聲稱棄用了 cookie 這種落后的東西;一個移動端項目,服務(wù)器給出的 API 中需要客戶端模擬一個 cookie,從而像瀏覽器中 ajax 那樣消費 API。
互聯(lián)網(wǎng)是基于 HTTP 協(xié)議構(gòu)建的,而 HTTP 協(xié)議因為簡單流行開來,但是 HTTP 協(xié)議是無狀態(tài)(通信層面上虛電路比數(shù)據(jù)報昂貴太多)的。為此人們?yōu)榱俗粉櫽脩粝氤隽烁鞣N辦法,包括 :cookie/session 機制、token、flash 跨瀏覽器 cookie 甚至瀏覽器指紋等。
把用戶身份藏在每一個地方(瀏覽器指紋技術(shù)甚至不需要存儲介質(zhì))
認證、授權(quán)、憑證
首先,認證和授權(quán)是兩個不同的概念,為了讓我們的 API 更加安全和具有清晰的設(shè)計,理解認證和授權(quán)的不同就非常有必要了,它們在英文中也是不同的單詞。
認證:是 authentication,指的是當前用戶的身份——當用戶登陸過后系統(tǒng)便能追蹤到他的身份做出符合相應(yīng)業(yè)務(wù)邏輯的操作。
即使用戶沒有登錄,大多數(shù)系統(tǒng)也會追蹤他的身份,只是當做來賓或者匿名用戶來處理。認證技術(shù)解決的是 “我是誰?”的問題。
授權(quán):與認證不同,授權(quán)是 authorization,指的是什么樣的身份被允許訪問某些資源,在獲取到用戶身份后繼續(xù)檢查用戶的權(quán)限。
單一的系統(tǒng)授權(quán)往往是伴隨認證來完成的,但是在開放 API 的多系統(tǒng)結(jié)構(gòu)下,授權(quán)可以由不同的系統(tǒng)來完成,例如 OAuth。授權(quán)技術(shù)是解決“我能做什么?”的問題。
實現(xiàn)認證和授權(quán)的基礎(chǔ)是需要一種媒介(credentials)來標記訪問者的身份或權(quán)利,在現(xiàn)實生活中每個人都需要一張身份證才能訪問自己的銀行賬戶、結(jié)婚和辦理養(yǎng)老保險等,這就是認證的憑證。
在古代軍事活動中,皇帝會給出戰(zhàn)的將軍頒發(fā)兵符,下級將領(lǐng)不關(guān)心持有兵符的人,只需要執(zhí)行兵符對應(yīng)的命令即可。在互聯(lián)網(wǎng)世界中,服務(wù)器為每一個訪問者頒發(fā) session ID 存放到 cookie,這就是一種憑證技術(shù)。
數(shù)字憑證還表現(xiàn)在方方面面:SSH 登錄的密匙、JWT 令牌、一次性密碼等。
用戶賬戶也不一定是存放在數(shù)據(jù)庫中的一張表,在一些企業(yè) IT 系統(tǒng)中,對賬戶管理和權(quán)限有了更多的要求。所以,賬戶技術(shù) (accounting)可以幫助我們使用不同的方式管理用戶賬戶,同時具有不同系統(tǒng)之間共享賬戶的能力,例如:微軟的活動目錄(AD),以及簡單目錄訪問協(xié)議(LDAP),甚至區(qū)塊鏈技術(shù)。
還有一個重要的概念是:訪問控制策略(AC)。如果我們需要把資源的權(quán)限劃分到一個很細的粒度,就不得不考慮用戶以何種身份來訪問受限的資源,選擇基于訪問控制列表(ACL)還是基于用戶角色的訪問控制(RBAC)或者其他訪問控制策略。
在流行的技術(shù)和框架中,這些概念都無法孤立的被實現(xiàn),因此在現(xiàn)實中使用這些技術(shù)時,大家往往為一個 OAuth2 是認證還是授權(quán)這種概念爭論不休。
為了容易理解,我在文末附上了一份常見技術(shù)和概念的術(shù)語表。
下面,我會介紹在API開發(fā)中常常使用的幾種認證和授權(quán)技術(shù):HTTP Basic AUthentication、HAMC、OAuth2,以及憑證技術(shù)JWT token。
HTTP Basic Authentication
你一定用過這種方式,但不一定知道它是什么。
在不久之前,當你訪問一臺家用路由器的管理界面,往往會看到一個瀏覽器彈出表單,要求你輸入用戶密碼。
在這背后,當用戶輸入完用戶名密碼后,瀏覽器幫你做了一個非常簡單的操作:
- 組合用戶名和密碼然后 Base64 編碼。
- 給編碼后的字符串添加 Basic 前綴,然后設(shè)置名稱為 Authorization 的 header 頭部。
API 也可以非常簡單的提供 HTTP Basic Authentication 認證方式,那么客戶端可以很簡單通過 Base64 傳輸用戶名和密碼即可:。
- 將用戶名和密碼使用冒號連接,例如 :username:abc123456。
- 為了防止用戶名或者密碼中存在超出 ASCII 碼范圍的字符,推薦使用UTF-8編碼。
- 將上面的字符串使用 Base 64 編碼,例如:dXNlcm5hbWU6YWJjMTIzNDU2。
- 在 HTTP 請求頭中加入 “Basic + 編碼后的字符串”——即:Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l。
這種方式實現(xiàn)起來非常簡單,在大量場景下被采用。
當然缺點也很明顯,Base64 只能稱為編碼,而不是加密 (實際上,無需配置密匙的客戶端并沒有任何可靠地加密方式,我們都依賴 TSL 協(xié)議)。
這種方式的致命弱點是:編碼后的密碼,如果明文傳輸則容易在網(wǎng)絡(luò)傳輸中泄露,在密碼不會過期的情況下,密碼一旦泄露,只能通過修改密碼的方式。
HMAC(AK/SK)認證
在我們對接一些 PASS 平臺和支付平臺時,會要求我們預(yù)先生成一個 access key(AK) 和 secure key(SK),然后通過簽名的方式完成認證請求。這種方式可以避免傳輸 secure key,且大多數(shù)情況下簽名只允許使用一次,避免了重放攻擊。
這種基于 AK/SK 的認證方式主要是利用散列的消息認證碼 (Hash-based MessageAuthentication Code) 來實現(xiàn)的。因此,有很多地方叫 HMAC 認證,實際上不是非常準確。
HMAC 只是利用帶有 key 值的哈希算法生成消息摘要,在設(shè)計 API 時有具體不同的實現(xiàn)。
HMAC 在作為網(wǎng)絡(luò)通信的認證設(shè)計中作為憑證生成算法使用,避免了口令等敏感信息在網(wǎng)絡(luò)中傳輸。
基本過程如下:
- 客戶端需要在認證服務(wù)器中預(yù)先設(shè)置 access key(AK 或叫 app ID) 和 secure key(SK)。
- 在調(diào)用 API 時,客戶端需要對參數(shù)和 access key 進行自然排序后并使用 secure key 進行簽名生成一個額外的參數(shù) digest。
- 服務(wù)器根據(jù)預(yù)先設(shè)置的 secure key 進行同樣的摘要計算,并要求結(jié)果完全一致。
- 注意 secure key 不能在網(wǎng)絡(luò)中傳輸,以及在不受信任的位置存放(瀏覽器等)。
為了讓每一次請求的簽名變得獨一無二,從而實現(xiàn)重放攻擊,我們需要在簽名時放入一些干擾信息。
在業(yè)界標準中有兩種典型的做法,質(zhì)疑/應(yīng)答算法(OCRA: OATH Challenge-Response Algorithm)、基于時間的一次性密碼算法(TOTP:Time-based One-time Password Algorithm)。
1. 質(zhì)疑/應(yīng)答算法
質(zhì)疑/應(yīng)答算法需要客戶端先請求一次服務(wù)器,獲得一個 401 未認證的返回,并得到一個隨機字符串(nonce)。
將 nonce 附加到按照上面說到的方法進行 HMAC 簽名,服務(wù)器使用預(yù)先分配的 nonce 同樣進行簽名校驗,這個 nonce 在服務(wù)器只會被使用一次,因此可以提供唯一的摘要。
2. 基于時間的一次性密碼認證
為了避免額外的請求來獲取 nonce,還有一種算法是使用時間戳,并且通過同步時間的方式協(xié)商到一致,在一定的時間窗口內(nèi)有效(1分鐘左右)。
這里的只是利用時間戳作為驗證的時間窗口,并不能嚴格的算作基于時間的一次性密碼算法。
標準的基于時間的一次性密碼算法在兩步驗證中被大量使用,例如:Google 身份驗證器不需要網(wǎng)絡(luò)通信也能實現(xiàn)驗證(但依賴準確的授時服務(wù))。
原理是:客戶端服務(wù)器共享密鑰然后根據(jù)時間窗口能通過 HMAC 算法計算出一個相同的驗證碼。
TOTP 基本原理和常見廠商
OAuth2 和 Open ID
OAuth(開放授權(quán))是一個開放標準,允許用戶授權(quán)第三方網(wǎng)站訪問他們存儲在另外的服務(wù)提供者上的信息,而不需要將用戶名和密碼提供給第三方網(wǎng)站或分享他們數(shù)據(jù)的所有內(nèi)容。
OAuth 是一個授權(quán)標準,而不是認證標準。提供資源的服務(wù)器不需要知道確切的用戶身份(session),只需要驗證授權(quán)服務(wù)器授予的權(quán)限(token)即可。
上圖只是 OAuth 的一個簡化流程,OAuth 的基本思路就是通過授權(quán)服務(wù)器獲取 access token 和 refresh token(refresh token 用于重新刷新access token),然后通過 access token 從資源服務(wù)器獲取數(shù)據(jù) 。
在特定的場景下還有下面幾種模式:
- 授權(quán)碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
如果需要獲取用戶的認證信息,OAuth 本身沒有定義這部分內(nèi)容,如果需要識別用戶信息,則需要借助另外的認證層,例如: OpenID Connect。
1. 驗證 access token
在一些介紹OAuth 的博客中,很少講到:資源服務(wù)器是怎么驗證 access token 的?
OAuth core 標準并沒有定義這部分,不過在 OAuth 其他標準文件中提到兩種驗證 access token的方式。
1)在完成授權(quán)流程后,資源服務(wù)器可以使用 OAuth 服務(wù)器提供的 Introspection 接口來驗證access token,OAuth服務(wù)器會返回 access token 的狀態(tài)以及過期時間。
在OAuth標準中驗證 token 的術(shù)語是 Introspection。同時,也需要注意 access token 是用戶和資源服務(wù)器之間的憑證,不是資源服務(wù)器和授權(quán)服務(wù)器之間的憑證。資源服務(wù)器和授權(quán)服務(wù)器之間應(yīng)該使用額外的認證(例如:Basic 認證)。
2)使用 JWT 驗證:授權(quán)服務(wù)器使用私鑰簽發(fā) JWT 形式的 access token,資源服務(wù)器需要使用預(yù)先配置的公鑰校驗 JWT token,并得到 token 狀態(tài)和一些被包含在 access token 中信息。因此,在 JWT 的方案下,資源服務(wù)器和授權(quán)服務(wù)器不再需要通信,在一些場景下帶來巨大的優(yōu)勢。同時,JWT 也有一些弱點,我會在JWT 的部分解釋。
2. refresh token 和 access token
幾乎所有人剛開始了解 OAuth 時都有一個一疑問:為什么已經(jīng)有了 access token 還需要 refresh token 呢?
授權(quán)服務(wù)器會在第一次授權(quán)請求時,一起返回 access token 和refresh token,在后面刷新 access token 時只需要 refresh token。
access token 和 refresh token 的設(shè)計意圖是不一樣的,access token 被設(shè)計用來客戶端和資源服務(wù)器之間交互,而 refresh token 是被設(shè)計用來客戶端和授權(quán)服務(wù)器之間交互。
某些授權(quán)模式下, access token 需要暴露給瀏覽器,充當一個資源服務(wù)器和瀏覽器之間的臨時會話,瀏覽器和資源服務(wù)器之間不存在簽名機制,access token 成為唯一憑證。因此,access token 的過期時間(TTL)應(yīng)該盡量短,從而避免用戶的 access token 被嗅探攻擊。
由于要求 access token 時間很短,refresh token 可以幫助用戶維護一個較長時間的狀態(tài),避免頻繁重新授權(quán)。
大家會覺得讓 access token 保持一個長的過期時間不就可以了嗎?
實際上,refresh token 和 access token 的不同之處在于:即使 refresh token 被截獲,系統(tǒng)依然是安全的,客戶端拿著 refresh token 去獲取 access token 時,同時需要預(yù)先配置的 secure key,客戶端和授權(quán)服務(wù)器之前始終存在安全的認證。
3. OAuth、Open ID、OpenID Connect
認證方面的術(shù)語實在太多,我在搭建自己的認證服務(wù)器或接入第三方認證平臺時,有時候到完成開發(fā)工作的最后一刻都無法理解這些術(shù)語。
OAuth 負責(zé)解決分布式系統(tǒng)之間的授權(quán)問題,即使有時候客戶端和資源服務(wù)器或者認證服務(wù)器存在同一臺機器上。OAuth 沒有解決認證的問題,但提供了良好的設(shè)計利于和現(xiàn)有的認證系統(tǒng)對接。
Open ID 解決的問題是:分布式系統(tǒng)之間身份認證問題,使用Open ID token 能在多個系統(tǒng)之間驗證用戶,以及返回用戶信息,可以獨立使用,與 OAuth 沒有關(guān)聯(lián)。
OpenID Connect 解決的是:在 OAuth 這套體系下的用戶認證問題,實現(xiàn)的基本原理是將用戶的認證信息(ID token)當做資源處理。在 OAuth 框架下完成授權(quán)后,再通過 access token 獲取用戶的身份。
這三個概念之間的關(guān)系有點難以理解,用現(xiàn)實場景來說:如果系統(tǒng)中需要一套獨立的認證系統(tǒng),并不需要多系統(tǒng)之間的授權(quán)可以直接采用 Open ID。
如果使用了 OAuth 作為授權(quán)標準,可以再通過 OpenID Connect 來完成用戶的認證。
JWT
在 OAuth 等分布式的認證、授權(quán)體系下,對憑證技術(shù)有了更多的要求,比如:包含用戶 ID、過期等信息,不需要再外部存儲中關(guān)聯(lián)。
因此,業(yè)界對 token 做了進一步優(yōu)化,設(shè)計了一種自包含令牌,令牌簽發(fā)后無需從服務(wù)器存儲中檢查是否合法,通過解析令牌就能獲取令牌的過期、有效等信息,這就是JWT (JSON Web Token)。
JWT 是一種包含令牌(self-contained token),或者叫值令牌 (value token),我們以前使用關(guān)聯(lián)到 session 上的 hash 值被叫做引用令牌(reference token)。
簡而言之,一個基本的JWT令牌為一段點分3段式結(jié)構(gòu)。
生成JWT 令牌的流程為:
- header json 的 base64 編碼為令牌第一部分。
- payload json 的 base64 編碼為令牌第二部分。
- 拼裝第一、第二部分編碼后的 json 以及 secret 進行簽名的令牌的第三部分。
因此,只需要簽名的 secret key 就能校驗 JWT 令牌,如果在消息體中加入用戶 ID、過期信息就可以實現(xiàn)驗證令牌是否有效、過期了,無需從數(shù)據(jù)庫/緩存中讀取信息。因為使用了加密算法,所以第一、二部分即使被修改(包括過期信息)也無法通過驗證。
JWT 優(yōu)點是:不僅可以作為 token 使用,同時也可以承載一些必要信息,省去多次查詢。
注意:
- JWT token 的第一、二部分只是 base64 編碼,肉眼不可讀,不應(yīng)當存放敏感信息。
- JWT token 的自包含特性,導(dǎo)致了無法被撤回。
- JWT 的簽名算法可以自己擬定,為了便于調(diào)試,本地環(huán)境可以使用對稱加密算法,生產(chǎn)環(huán)境建議使用非對稱加密算法。
JWT token 在微服務(wù)的系統(tǒng)中優(yōu)勢特別突出:多層調(diào)用的 API 中可以直接傳遞 JWT token,利用自包含的能力,可以減少用戶信息查詢次數(shù);更重要的是,使用非對稱的加密方式可以通過在系統(tǒng)中分發(fā)密匙的方式驗證 JWT token。
當然,OAuth 對 access token 等憑證所選用的技術(shù)并沒有做出限制,OAuth 并不強制使用 JWT,在使用 JWT 自包含特性的優(yōu)勢時,必須考慮到 JWT 撤回困難的問題。在一些對撤回 token 要求很高的項目中不適合使用JWT,即使采用了一些方案實現(xiàn)(whitelist 和 blacklist)也違背了設(shè)計 JWT 的初衷。
Cookie 、Token in Cookie、Session Token 依然被使用
在構(gòu)建 API 時,開發(fā)者會發(fā)現(xiàn)我們的認證方式和網(wǎng)頁應(yīng)用有一些不同,除了像 ajax 這種典型的 web 技術(shù)外,如果我們希望 API 是無狀態(tài)的,不推薦使用 Cookie。
使用 Cookie 的本質(zhì)是用戶第一次訪問時服務(wù)器會分配一個 Session ID,后面的請求中客戶端都會帶上這個 ID 作為當前用戶的標志。因為 HTTP 本身是無狀態(tài)的,Cookie 屬于一種內(nèi)建于瀏覽器中實現(xiàn)狀態(tài)的方式。如果我們的 API 是用來給客戶端使用的,強行要求 API 的調(diào)用者管理Cookie 也可以完成任務(wù)。
在一些遺留或者不是標準的認證實現(xiàn)的項目中,我們依然可以看到這些做法,快速地實現(xiàn)認證。
- 使用 cookie,例如 web 項目中 ajax 的方式。
- 使用 session ID 或 hash 作為 token,但將 token 放入 header 中傳遞。
- 將生成的 token (可能是JWT)放入 cookie 傳遞,利用 HTTPonly 和 Secure 標簽保護 token。
選擇合適的認證方式
隨著微服務(wù)的發(fā)展,API 的設(shè)計不僅僅是面向 WEB 或者 Mobile APP,還有BFF(Backend for Frontend)和 Domain API 的認證,以及第三方服務(wù)的集成。
客戶端到服務(wù)器之間認證和服務(wù)器到服務(wù)器之間認證是不同的。
我們把終端用戶(Human)參與的通信,叫做 Human-to-machine (H2M),服務(wù)器與服務(wù)器之間的通信叫做 Machine-to-machine (M2M)。
H2M 的通信需要更高的安全性,M2M 的通信天然比 H2M 安全,因此更多的強調(diào)性能,在不同的場合下選擇合適的認證技術(shù)就顯得特別重要。例如: HTTP Basic Authentication 用來作為 H2M 認證顯得有些落后,但是在 M2M 中被大量使用。
另外值得一提的是:H2M 這種通信方式下,客戶端不受控制,由于無法自主分發(fā)密匙,認證通信的安全高度依賴 HTTPS。
從一個宏觀的角度看待他們的關(guān)系,對我們技術(shù)選型非常有幫助。
術(shù)語表
- Browser fingerprinting 通過查詢?yōu)g覽器的代理字符串,屏幕色深,語言等,然后這些值通過散列函數(shù)傳遞產(chǎn)生指紋,不需要通過 Cookie 就可以識別瀏覽器。
- MAC(Message authentication code) 在密碼學(xué)中,訊息鑒別碼,是經(jīng)過特定算法后產(chǎn)生的一小段資訊,檢查某段訊息的完整性。
- 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林寧 原創(chuàng)發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)允許,禁止轉(zhuǎn)載。
題圖來自Unsplash, 基于CC0協(xié)議
test
??