全文轉載:《細說API – 認證、授權和憑證》 - THOUGHTWORKS
互聯網基於HTTP協議構建,而HTTP協議因為簡單而流行開來,但是HTTP協議是無狀態的,為此開發者為了追蹤用戶想出了各種辦法,包括Cookie/Session機制、Token、Flash跨瀏覽器Cookie,甚至瀏覽器指紋等。
更有甚者,把userID存儲到LocalStorage中當做Token使用;移動端項目,伺服器給出的API中需要客戶端模擬Cookie,從而像瀏覽器中Ajax方式調用API。
首先,認證和授權是兩個不同的概念,為了讓API更加安全和具有清晰的設計,理解認證和授權的不同就非常有必要了,它們在英文中也是不同的單詞。
認證指的是當前用戶的身份,當用戶登陸過後系統便能追蹤到他的身份做出符合相應業務邏輯的操作。即使用戶沒有登錄,大多數系統也會追蹤他的身份,只是當做來賓或者匿名用戶來處理。認證技術解決的是「我是誰」的問題。
授權指的是什麼樣的身份被允許訪問某些資源,在獲取到用戶身份後繼續檢查用戶的許可權。單一的系統授權往往是伴隨認證來完成的,但是在開放API的多系統結構下,授權可以由不同的系統來完成,例如OAuth。授權技術是解決「我能做什麼」的問題。
實現認證和授權的基礎是需要一種媒介(credentials)來標記訪問者的身份或權利,在現實生活中每個人都需要一張身份證才能訪問自己的銀行賬戶、結婚和辦理養老保險等,這就是認證的憑證;在古代軍事活動中,皇帝會給出戰的將軍頒發兵符,下級將領不關心持有兵符的人,只需要執行兵符對應的命令即可。在互聯網世界中,伺服器為每一個訪問者頒發Session ID存放到Cookie,這就是一種憑證技術。數字憑證還表現在方方面面,SSH登錄的密匙、JWT令牌、一次性密碼等。
如果需要把資源的許可權劃分到一個很細的粒度,就不得不考慮用戶以何種身份來訪問受限的資源,選擇基於訪問控制列表(ACL)還是基於用戶角色的訪問控制(RBAC)或者其他訪問控制策略。
在流行的技術和框架中,這些概念都無法孤立的被實現,API開發中常常使用的認證和授權技術:HTTP Basic AUthentication、HAMC、OAuth2,以及憑證技術JWT Token。
傳統的login表單,當用戶輸入完用戶名密碼後,瀏覽器做了非常簡單的操作:
API也可以提供非常簡單的HTTP Basic Authentication認證方式:
這種方式實現起來非常簡單,在大量場景下被採用。
缺點也很明顯,Base64隻能稱為編碼,而不是加密。這種方式的致命弱點是編碼後的密碼如果明文傳輸則容易在網路傳輸中泄露,在密碼不會過期的情況下,密碼一旦泄露,只能修改密碼。
在對接一些PASS平臺和支付平臺時,會要求預先生成一個Access Key和Secure Key,然後通過簽名的方式完成認證請求,這種方式可以避免傳輸Secure Key,且大多數情況下簽名只允許使用一次,避免了重放攻擊。
基於AK/SK的認證方式主要是利用散列的消息認證碼(Hash-based MessageAuthentication Code)來實現,因此有很多地方叫HMAC認證,實際上不是非常準確。HMAC只是利用帶有Key值的哈希演算法生成消息摘要,在設計API時有不同的具體實現。
HMAC在作為網路通信的認證設計中作為憑證生成演算法使用,避免了口令等敏感信息在網路中傳輸,基本過程如下:
為了讓每一次請求的簽名變得獨一無二,從而避免重放攻擊,需要在簽名時放入一些幹擾信息。
業界標準中有兩種典型的做法,質疑/應答演算法、基於時間的一次性密碼演算法。
質疑/應答演算法需要客戶端先請求一次伺服器,獲得一個401未認證的返回,並得到一個隨機字元串(nonce)。將nonce附加到按照上面說到的方法進行HMAC簽名,伺服器使用預先分配的nonce同樣進行簽名校驗,這個nonce在伺服器只會被使用一次,因此可以提供唯一的摘要。
為了避免額外的請求來獲取nonce,還有一種演算法是使用時間戳,並且通過同步時間的方式協商到一致,在一定的時間窗口內有效。
利用時間戳作為驗證的時間窗口,並不能嚴格的算作基於時間的一次性密碼演算法。標準的基於時間的一次性密碼演算法在兩步驗證中被大量使用,例如Google身份驗證器不需要網路通信也能實現驗證(但依賴準確的授時服務)。原理是客戶端伺服器共享密鑰然後根據時間窗口能通過HMAC演算法計算出一個相同的驗證碼。
OAuth(開放授權)是一個開放標準,允許用戶授權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方網站或分享他們數據的所有內容。
OAuth是一個授權標準,而不是認證標準。提供資源的伺服器不需要知道確切的用戶身份(Session),只需要驗證授權伺服器授予的許可權(Token)即可。
上圖是OAuth的一個簡化流程,OAuth的基本思路就是通過授權伺服器獲取Access Token和Refresh Token(Refresh Token用於重新刷新Access Token,然後通過Access Token從資源伺服器獲取數據。
在特定的場景下還有下面幾種模式:
如果需要獲取用戶的認證信息,OAuth本身沒有定義這部分內容,如果需要識別用戶信息,則需要藉助另外的認證層,例如OpenID Connect。
OAuth中資源伺服器如何驗證Access Token,OAuth Core標準並沒有定義這部分,不過在OAuth其他標準文件中提到兩種驗證Access Token的方式。
授權伺服器會在第一次授權請求時一起返回Access Token和Refresh Token,在後面刷新Access Token時只需要RefreshToken。Access Token和Refresh Token的設計意圖是不一樣的,Access Token被設計用來客戶端和資源伺服器之間交互,而Refresh Token是被設計用來客戶端和授權伺服器之間交互。
某些授權模式下Access Token需要暴露給瀏覽器,充當一個資源伺服器和瀏覽器之間的臨時會話,瀏覽器和資源伺服器之間不存在簽名機制,Access Token成為唯一憑證,因此Access Token的過期時間(TTL)應該盡量短,從而避免用戶的Access Token被嗅探攻擊。
由於要求Access Token時間很短,Refresh Token可以幫助用戶維護一個較長時間的狀態,避免頻繁重新授權。實際上Refresh Token和Access Token的不同之處在於即使Access Token被截獲,系統依然是安全的,客戶端拿著Refresh Token去獲取Access Token時同時需要預先配置的Secure Key,客戶端和授權伺服器之前始終存在安全的認證。
OAuth負責解決分散式系統之間的授權問題,即使有時候客戶端和資源伺服器或者認證伺服器存在同一臺機器上。OAuth沒有解決認證的問題,但提供了良好的設計利於和現有的認證系統對接。
Open ID解決的問題是分散式系統之間身份認證問題,使用Open ID Token能在多個系統之間驗證用戶,以及返回用戶信息,可以獨立使用,與OAuth沒有關聯。
OpenID Connect解決的是在OAuth這套體系下的用戶認證問題,實現的基本原理是將用戶的認證信息(ID token)當做資源處理。在OAuth框架下完成授權後,再通過Access Token獲取用戶的身份。
這三個概念之間的關係有點難以理解,用現實場景來說,如果系統中需要一套獨立的認證系統,並不需要多系統之間的授權可以直接採用Open ID。如果使用了OAuth作為授權標準,可以再通過OpenID Connect來完成用戶的認證。
在OAuth等分散式的認證、授權體系下,對憑證技術有了更多的要求,比如包含用戶ID、過期等信息,不需要再外部存儲中關聯。因此業界對Token做了進一步優化,設計了一種自包含令牌,令牌簽發後無需從伺服器存儲中檢查是否合法,通過解析令牌就能獲取令牌的過期、有效等信息,這就是JWT(JSON Web Token)。
JWT是一種包含令牌(self-contained token),或者叫值令牌(value token),關聯到Session上的Hash值被叫做引用令牌(reference token)。
簡而言之,一個基本的JWT令牌為一段點分3段式結構。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
生成JWT令牌的流程為:
因此只需要簽名的secret key就能校驗JWT令牌,如果在消息體中加入用戶ID、過期信息就可以實現驗證令牌是否有效、過期了,無需從資料庫/緩存中讀取信息。因為使用了加密演算法,所以第一、二部分即使被修改(包括過期信息)也無法通過驗證。JWT優點是不僅可以作為Token使用,同時也可以承載一些必要信息,省去多次查詢。
PS:
JWT Token在微服務的系統中優勢特別突出。多層調用的API中可以直接傳遞JWT Token,利用自包含的能力,可以減少用戶信息查詢次數;更重要的是,使用非對稱的加密方式可以通過在系統中分發密匙的方式驗證JWT token。
OAuth對Access Token等憑證所選用的技術並沒有做出限制,OAuth並不強制使用JWT,在使用JWT自包含特性的優勢時,必須考慮到JWT撤回困難的問題。在一些對撤回Token要求很高的項目中不適合使用JWT,即使採用了一些方案實現(WhiteList 和 BlackList)也違背了設計JWT的初衷。
在構建API時,認證方式和網頁應用有一些不同,除了像Ajax這種web技術外,如果希望API是無狀態的,則不推薦使用Cookie。
使用Cookie的本質是用戶第一次訪問時伺服器會分配一個Session ID,後面的請求中客戶端都會帶上這個ID作為當前用戶的標誌,因為HTTP本身是無狀態的,Cookie屬於一種內建於瀏覽器中實現狀態的方式。如果API是用來給客戶端使用的,強行要求API的調用者管理Cookie也可以完成任務。
在一些遺留或者不是標準的認證實現的項目中,依然可以看到這些做法,快速地實現認證。
隨著微服務的發展,API的設計不僅僅是面向web或者Mobile App,還有BFF(Backend for Frontend)和Domain API的認證,以及第三方服務的集成。
客戶端到伺服器之間認證和伺服器到伺服器之間認證是不同的。
把終端用戶參與的通信,叫做Human-to-machine(H2M),伺服器與伺服器之間的通信叫做Machine-to-machine(M2M)。
H2M的通信需要更高的安全性,M2M的通信天然比H2M安全,因此更多的強調性能,在不同的場合下選擇合適的認證技術就顯得特別重要。例如HTTP Basic Authentication用來作為H2M認證顯得有些落後,但是在M2M中被大量使用。
另外值得一提的是,H2M這種通信方式下,客戶端不受控制,由於無法自主分發密匙,認證通信的安全高度依賴HTTPS。
參考:
shieldfy/API-Security-Checklist
https://swagger.io/docs/specification/authentication/basic-authentication/
推薦閱讀: