本篇我們對常用的身份認證協議做簡要的梳理,包括主流的 HTTP 相關認證協議以及證書密鑰對、新興的 WebAuthn 認證。
HTTP 協議認證
RFC 7235 中定義了 HTTP 協議的認證框架,要求在支持 HTTP 協議的服務器,如果訪問服務的身份驗證失敗,需要返回 401 Unauthorized 或 407 Proxy Authentication Required 狀態碼,并告知客戶端應該采用何種方案提供憑證信息,收到響應后客戶端按要求加入認證憑據信息后才能繼續訪問。
# 響應頭
WWW-Authenticate: <認證方案> realm=<保護區域的描述信息>
Proxy-Authenticate: <認證方案> realm=<保護區域的描述信息># 請求頭
Authorization: <認證方案> <憑證內容>
Proxy-Authorization: <認證方案> <憑證內容>
整個認證流程如下:
在上述認證框架的基礎上,HTTP 提出了不同的認證方式。
HTTP Basic & 摘要認證
RFC 2617 提出了 Basic 和 Digest Access(摘要認證)兩種方式,我們先來看下 Basic,其認證流程如下:
- 將用戶名密碼用冒號間隔做拼接,格式為
username:password
。 - 對拼接后的字符串進行 Base64 編碼,比如
Base64(admin:12345)
得到YWRtaW46MTIzNDU=
。 - 將編碼后的字符串添加
Basic
標識后放到 HTTP 頭 Authorization 中,最終結果為Authorization: Basic YWRtaW46MTIzNDU=
,然后向服務端請求。 - 如果認證通過,則返回 200 響應碼。否則按照上述框架要求的,響應 401 并且返回響應頭
WWW-Authenticate: Basic realm="Dev", charset="UTF-8"
。
可以看到 Basic 認證對密碼只是做了編碼,并沒有加密處理,因此使用 Basic 認證時必須結合 TLS 加密傳輸一起使用。Basic 更多用于系統內部之間一些組件的訪問,在實際生產系統中很少使用。
RFC2069 提出了 Digest Access 摘要認證,后續由 RFC 2617 做了一系列的增強,算是對 Basic 的改進,其認證流程如下:
- 認證失敗,服務端返回 401 以及
WWW-Authenticate
頭如下。
WWW-Authenticate: Digest realm="testrealm@host.com",qop="auth,auth-int",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",opaque="5ccc069c403ebaf9f0171e9517f40e41"
如果沒有特殊要求,其計算的流程如下:
HA1 = MD5(username:realm:password)
HA2 = MD5(method:digestURI)
response = MD5(HA1:nonce:HA2)
其中,nonce 是服務端返回的鹽值,method 是請求方法,digestURI 為請求 URI。RFC 2617 提出了
qop(quality of protection,保護質量) 對計算方式提出了更復雜的要求,改進后的計算流程如下:
-
客戶端生成自己的鹽值,然后做哈希操作
HA1 = MD5(MD5(username:realm:password):nonce:cnonce)
。 -
如果 qop 包含了
auth-init
,則HA2 = MD5(method:digestURI:MD5(entityBody))
,entityBody 代表整個請求體。 -
最后
response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2)
。計算完成后客戶端將值加入到Authorization
請求頭中,示例如下:
GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username="Mufasa",realm="testrealm@host.com",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",uri="/dir/index.html",qop=auth,nc=00000001,cnonce="0a4f113b",response="6629fae49393a05397450978507c4ef1",opaque="5ccc069c403ebaf9f0171e9517f40e41"
可以看到摘要認證通過鹽值、MD5 哈希的方式對用戶名密碼傳輸做了一定程度的加密,但其實最終加密后的強度還是取決于密碼的強度,如果密碼強度較弱其依然有泄露的風險,另外這里也沒有辦法避免中間人攻擊。
Bearer(OAuth 2.0)認證
RFC 6750 描述了基于 OAuth 2.0 的認證授權方式,它要求使用 Bearer Token(承載令牌)
的方式進行認證。
客戶端在獲取令牌成功后,需要將令牌放到 Authorization
請求頭中,格式如下:
Authorization: Bearer <token>
OAuth 2.0 協議當初主要是為了對第三方授權而實現的,其同時設計身份認證和授權,這個我們后面在詳細介紹。
Form 認證
因為身份認證通常是應用系統的業務邏輯的一部分,雖然 HTTP 協議提供了基本的認證框架,但在大多數情況下,我們需要自行實現認證流程。最常見的方式就是通過 HTML 表單提交用戶名和密碼,然后服務端驗證成功后返回一個憑據給客戶端,客戶端在后續的請求中將憑據放到 HTTP 請求頭中。這在萬維網中被稱為 Web 認證,因為最常見的方式就是通過表單登錄,也叫做表單認證(Form-based Authentication)。
表單認證并沒有一個標準的規范規范,因此通常由產品和工程師根據業務需求自行設計實現,在設計方案時可以參考 OWASP Authentication Cheat Sheet 中的建議。
Web WebAuthn 認證
證書密鑰對
上述認證方式都是基于用戶名密碼或者用戶生物特征等方式進行認證的,這在ToC(面向消費者)場景下是比較常見的方式,但在其他場景下,比如系統內部的服務間通信、API 調用,機構和機構之前的通信,則需要其他的解決方案。最常用的就是數字證書和密鑰對認證。
通過非對稱加密的方式,我們可以生成公鑰和私鑰,我們將私鑰安全保存,然后將公鑰分發出去。通過私鑰加密的數據只有使用公鑰才能解密,這樣同時解決了身份認證和數據加密的問題。但這種方式無法避免中間人攻擊,因此通常需要結合數字證書來使用。
我們需要一個權威證書機構(CA,Certificate Authority)來頒發證書,證書中包含了公鑰和一些其他信息,比如證書的有效期、頒發者等。通信雙方在拿到證書后,可以向 CA 機構驗證證書的合法性,驗證通過后就可以使用公鑰進行加密通信,從而保證通信的安全性。
目前網站和應用系統中使用的 HTTPS 通信以及云原生的下的 mTLS(雙向 TLS)認證都是基于證書密鑰對的方式。
傳統網站的 HTTPS 單向認證流程如下:
- 我們在瀏覽器訪問網站,發起 HTTPS 請求。
- 服務端收到請求后,返回自己的證書給瀏覽器。
- 瀏覽器驗證證書的合法性,如果合法則繼續請求,否則返回錯誤。
- 瀏覽器使用證書中的公鑰加密請求數據,并發送給服務端。
可以看到這里主要是瀏覽器客戶端去驗證服務端的證書是否合法,也就是驗證我們訪問的網站是否是真實合法的。而在 mTLS 雙向認證中,除了客戶端驗證服務端的證書外,服務端也會驗證客戶端的證書是否合法,流程如下:
- 客戶端請求服務端
- 服務端返回其 TLS 證書
- 客戶端驗證服務端證書合法性
- 客戶端提供其 TLS 證書
- 服務端驗證客戶端證書合法性
- 驗證成功,雙方使用各自的公鑰進行加密通信。
在云原生環境以及 Zero Trust 安全架構理念的指導下,mTLS 幾乎已經成為服務間通信的的必備安全認證方式。通常在企業內部,我們可以通過自簽名證書的方式來生成 CA 和證書密鑰對,下面我們用 OpenSSL 命令工具來演示這個過程:
- 生成 CA 私鑰和證書
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt
- 生成服務端私鑰和證書簽名請求(CSR)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
- 生成服務端證書
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256
- 生成客戶端私鑰和證書簽名請求(CSR)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
- 生成客戶端證書
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256