全文轉載:《細說API – 認證、授權和憑證》 - THOUGHTWORKS

互聯網基於HTTP協議構建,而HTTP協議因為簡單而流行開來,但是HTTP協議是無狀態的,為此開發者為了追蹤用戶想出了各種辦法,包括Cookie/Session機制、Token、Flash跨瀏覽器Cookie,甚至瀏覽器指紋等。

更有甚者,把userID存儲到LocalStorage中當做Token使用;移動端項目,伺服器給出的API中需要客戶端模擬Cookie,從而像瀏覽器中Ajax方式調用API。

一、認證、授權、憑證

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

A、認證 - Authentication

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

B、授權 - Authorization

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

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

C、訪問控制策略 - AC

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

在流行的技術和框架中,這些概念都無法孤立的被實現,API開發中常常使用的認證和授權技術:HTTP Basic AUthentication、HAMC、OAuth2,以及憑證技術JWT Token。

二、HTTP Basic Authentication

傳統的login表單,當用戶輸入完用戶名密碼後,瀏覽器做了非常簡單的操作:

  1. 組合用戶名和密碼然後Base64編碼
  2. 給編碼後的字元串添加Basic前綴,然後設置名稱為Authorization的header頭部

API也可以提供非常簡單的HTTP Basic Authentication認證方式:

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

這種方式實現起來非常簡單,在大量場景下被採用。

缺點也很明顯,Base64隻能稱為編碼,而不是加密。這種方式的致命弱點是編碼後的密碼如果明文傳輸則容易在網路傳輸中泄露,在密碼不會過期的情況下,密碼一旦泄露,只能修改密碼。

三、HMAC(AK/SK)認證

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

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

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

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

為了讓每一次請求的簽名變得獨一無二,從而避免重放攻擊,需要在簽名時放入一些干擾信息。

業界標準中有兩種典型的做法,質疑/應答演算法、基於時間的一次性密碼演算法。

A、質疑/應答演算法

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

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

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

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

四、OAuth2和Open ID

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

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

上圖是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。

A、驗證Access Token

OAuth中資源伺服器如何驗證Access Token,OAuth Core標準並沒有定義這部分,不過在OAuth其他標準文件中提到兩種驗證Access Token的方式。

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

B、Refresh Token和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,客戶端和授權伺服器之前始終存在安全的認證。

C、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段式結構。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

生成JWT令牌的流程為:

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

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

PS:

  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-to-machine(H2M),伺服器與伺服器之間的通信叫做Machine-to-machine(M2M)。

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

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

八、安全API設計規範

A、身份認證

  1. 不使用basic auth使用標準的認證協議(如JWT、OAuth)
  2. 不再造authentication、token generating、password storing輪子,使用標準方案
  3. 在登錄中使用max retry和自動封禁功能
  4. 加密所有的敏感數據

B、JWT(JSON Web Token)

  1. 使用隨機複雜的密鑰(JWT Secret)以增加暴力破解的難度
  2. 不在請求體中直接提取數據,要對數據進行加密(HS256或RS256)
  3. 使token的過期時間盡量的短(TTL、RTTL)
  4. 不在JWT的請求體中存放敏感數據,它是可破解的

C、OAuth授權或認證協議

  1. 始終在後台驗證redirect_uri,只允許白名單的URL
  2. 每次交換令牌的時候不加token(不允許response_type=token)
  3. 使用state參數並填充隨機的哈希數來防止跨站請求偽造(CSRF)
  4. 對不同的應用分別定義默認的作用域和各自有效的作用域參數

D、訪問

  1. 限制流量來防止DDoS攻擊和暴力攻擊
  2. 在服務端使用HTTPS協議來防止MITM攻擊
  3. 使用HSTS協議防止SSLStrip攻擊

E、輸入

  1. 使用與操作相符的HTTP操作函數,GET(讀取)、POST(創建)、PUT(替換/更新)以及DELETE(刪除記錄),如果請求的方法不適用於請求的資源則返回405 Method Not Allowed
  2. 在請求頭中的content-type欄位使用內容驗證來只允許支持的格式(如application/xml、application/json等等)並在不滿足條件的時候返回406 Not Acceptable
  3. 驗證content-type的發布數據和收到的一樣(如application/x-www-form-urlencoded、multipart/form-data、application/json等等)
  4. 驗證用戶輸入來避免一些普通的易受攻擊缺陷(如XSS、SQL注入、遠程代碼執行等等)
  5. 不在URL中使用任何敏感的數據(credentials、Passwords、security tokens、API keys)而是使用標準的認證請求頭
  6. 使用一個API Gateway服務來啟用緩存、訪問速率限制(如Quota、Spike Arrest、Concurrent Rate Limit)以及動態地部署APIs resources

F、處理

  1. 檢查是否所有的終端都在身份認證之後,以避免被破壞了的認證體系
  2. 避免使用特有的資源id,使用/me/orders替代/user/654321/orders
  3. 使用UUID代替自增長的id
  4. 如果需要解析XML文件,確保實體解析(entity parsing)是關閉的以避免XXE攻擊
  5. 如果需要解析XML文件,確保實體擴展(entity expansion)是關閉的以避免通過指數實體擴展攻擊實現的Billion Laughs/XML bomb
  6. 在文件上傳中使用CDN
  7. 如果需要處理大量的數據,使用Workers和Queues來快速響應,從而避免HTTP阻塞
  8. 關閉DEBUG模式

G、輸出

  1. 發送X-Content-Type-Options: nosniff頭
  2. 發送X-Frame-Options: deny頭
  3. 發送Content-Security-Policy: default-src none頭
  4. 刪除指紋頭X-Powered-By、Server、X-AspNet-Version等等
  5. 在響應中強制使用content-type
  6. 不返回敏感的數據,如credentials、Passwords、security tokens
  7. 在操作結束時返回恰當的狀態碼(如200 OK、400 Bad Request、401 Unauthorized、405 Method Not Allowed等等)

H、持續集成和持續部署

  1. 使用單元測試和集成測試來審計設計和實現
  2. 引入代碼審查流程,不自行批准更改
  3. 在推送到生產環境之前確保服務的所有組件都用殺毒軟體靜態地掃描過,包括第三方庫和其它依賴
  4. 為部署設計一個回滾方案

I、其他

  1. 構建RESTful API的有用資源集合 - 參考yosriady/api-development-tools

九、術語表

  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)一次性密碼,例如註冊郵件和簡訊中的認證碼。

參考:

shieldfy/API-Security-Checklist

swagger.io/docs/specifi

推薦閱讀:

相关文章