聊聊 API 簽名方式

前言

現在越來越多的公司以 API 的形式對外提供服務,這些 API 接口大多暴露在公網上,所以安全性就變的很重要了。最直接的風險如下:

  • 非法使用 API 服務。(收費接口非法調用)
  • 惡意攻擊和破壞。(數據篡改、DOS)

因此需要設計一些接口安全保護的方式來增強接口安全,在運輸層可添加 SSL 證書,上 HTTPS,在應用層主要是通過一些加密邏輯來實現。目前主流的兩種是在 HTTP Header 里加認證信息和 API 簽名。

HTTP 簡單身份認證

在 HTTP 請求的 Header 中添加認證字段例如:

Authorization: 3F2504E04F8911D39A0C0305E82C3301

服務器處理前取出該字段進行校驗即可。

Spring Boot 項目直接實現一個攔截器就可進行判斷:

String token = request.getHeader("Authorization");
if (!Strings.isNullOrEmpty(token)) {
    hasAuth = redisTemplate.hasKey("userToken:" + token);
}

這類方法實現比較簡單,可以做基本的身份認證,防君子不防小人,可通過中間人攻擊獲得 Authorization。使用 HTTPS 安全性會得到提高,但是無法抵禦重放攻擊造成的影響,例如 DDOS。

API 簽名認證

API 簽名的方式較前一種要複雜的多,但是可解決的安全問題也更多:

  • 可校驗請求者的合法性。
  • 可以校驗參數的完整性和是否被篡改。
  • 可以防止重放攻擊。

part1:請求端加密

API 使用者會獲取到服務器頒發的 ak 和 sk 兩個祕鑰,ak 爲公鑰,sk 爲私鑰。

簽名有以下規則:

  1. 約定請求時會攜帶 ak 作爲參數並放入 HTTP Header。
  2. 請求參數處理:
    • GET:取出所有的參數,並根據 key 進行字典排序,拼裝成如下格式。
    • POST:如果是 application/x-www-form-urlencoded,直接取出和 GET 參數進行排序拼接,如果是 application/json,則直接將整個 json 串 md5 加密後再 base64。
GET:
strToSign = uri + "\n" + ak=ak&k1=v1&k2=v2&k3=v3
POST:
strToSign = uri + "\n" + ak=ak&k1=v1&k2=v2&k3=v3 + "\n" + base64(md5(json_body))
  1. 使用 HmacSHA256 算法,傳入 sk 進行簽名計算,sign = base64(HmacSHA256(K,strToSign)),其中K=sk
  2. 組裝 HTTP 請求,將 X-Ca-Key=ak,X-Ca-Signature=sign 添加到 HTTP Header 中進行請求。

一個簡單實例如下圖所示:

part2:服務端解密

同樣的,服務端獲取到請求的 ak 後,查詢出對應的 sk,使用相同的規則進行簽名計算,計算出的 sign 和傳入的 sign 比對,就能夠知道參數是否被篡改。

經過這樣簽名方式後,可以保證上文提到的,校驗請求者的合法性和校驗參數的完整性和是否被篡改。但是如果有人施加一箇中間人攻擊,就可以獲取到請求報文,即便攻擊者無法破譯出簽名規則,也可以將請求重放,也就是原封不動提交給服務器,那麼如果發起惡意大規模攻擊,就會使服務器產生拒絕服務。

更進一步

如果需要的安全性更高,可以採用 timestamp 和 nonce 來解決這個問題。

  • timestamp:很好理解,調用者傳入,服務端判斷,一段時間失效。
  • nonce:在信息安全中,nonce 是一個在加密通信只能使用一次的數字。

單一使用 timestamp,可以一定程度上減少重放攻擊的頻率,但是無法完全遏制。

單一使用 nonce,咋一眼看可以保證請求的唯一性,但實際上服務端,隨着時間推移服務端無法存儲大量的 nonce,需要進入淘汰環節,一旦舊的 nonce 被淘汰,那麼攻擊者依舊可以捲土重來進行重放攻擊。

因此,將兩者結合一起來就是最終的方案,服務端首先驗證 nonce 是否存在,再校驗時間戳是否在規定的期限內。如果舊 nonce 被清理,也有時間戳進行把關,使得請求無法被重放。

part1:請求端生成 timestamp 和 nonce

生成時需要保證短時間內生成 nonce 的唯一性。

將 timestamp 和 nonce 寫入 HTTP Header 中。

part3:服務端校驗

  1. 數據庫查詢請求帶上的 nonce 是否存在(推薦使用Redis,自帶TTL功能)。
  2. 如果不存在,且請求時間有效則爲合法請求,同時將 nonce 寫入,並記錄時間;如果不存在,且請求時間超出規定時限,判斷爲惡意請求。
  3. 如果已經存在,判斷爲惡意請求。

做足以上這幾部基本上已經可以保證一定的安全性。當然還有更復雜的,可以閱讀阿里的 Open API 簽名文檔,根據項目自身對於安全性的需求可以適當進行簡化。本文講到的基本邏輯就是根據阿里的來的。

API 簽名與 HTTPS

這邊還想提一下 HTTPS。之前看到一則知乎上的提問:使用了 https 後,還有必要對數據進行簽名來確保數據沒有被篡改嗎?

總結一下就是:

  • API 簽名保證的是應用的數據安全和防篡改,並且可以作爲業務的參數校驗和處理重放攻擊。

  • HTTPS 保證的是運輸層的加密傳輸,但是無法防禦重放攻擊。

換句話說,HTTPS 保證通過中間人攻擊抓到的報文是密文,無法或者說很難破解。但仍然可以將報文重發,形成 DDOS。同時,如果不簽名,只用 HTTP 簡單認證,通過抓包,直接可以獲取到 Authorization,就可以隨意發起請求了。因此最安全的方法就是結合 HTTPS 和 API 簽名。

總結

本文主要介紹了當前主流 API 簽名方式,可以根據項目場景去挑選組合合適的方案。

博客還附帶實現了一個根據上文規則描述的工具類,可以用於API簽名,可見下面源碼鏈接。如果需要使用 timestamp 和 nonce 的可在此基礎上將這兩個字段添加到 sortedMap 中一起拼接,並集成Redis。

源碼

參考文獻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章