跳轉到

最佳實踐

1. 簽名

為了防止 API 介面中的資料被篡改,很多時候我們需要對 API 介面做簽名

介面請求方將請求引數 + 時間戳 + 金鑰拼接成一個字串,然後通過md5等 hash 演算法,生成一個前面 sign。

然後在請求引數或者請求頭中,增加 sign 引數,傳遞給 API 介面。

API 介面的閘道器服務,獲取到該 sign 值,然後用相同的請求引數 + 時間戳 + 金鑰拼接成一個字串,用相同的 m5 演算法生成另外一個 sign,對比兩個 sign 值是否相等。

如果兩個 sign 相等,則認為是有效請求,API 介面的閘道器服務會將給請求轉發給相應的業務系統。

如果兩個 sign 不相等,則 API 介面的閘道器服務會直接返回簽名錯誤。

簽名中為什麼要加時間戳?

為了安全性考慮,防止同一次請求被反覆利用,增加了金鑰 1 被破解的可能性,我們必須要對每次請求都設定一個合理的過期時間,比如:15 分鐘。

這樣一次請求,在 15 分鐘之內是有效的,超過 15 分鐘,API 介面的閘道器服務會返回超過有效期的異常提示。

目前生成簽名中的金鑰有兩種形式:

一種是雙方約定一個固定值 privateKey。

另一種是 API 介面提供方給出 AK/SK 兩個值,雙方約定用 SK 作為簽名中的金鑰。AK 介面呼叫方作為 header 中的 accessKey 傳遞給 API 介面提供方,這樣 API 介面提供方可以根據 AK 獲取到 SK,而生成新的 sgin。

2. 加密

有些時候,我們的 API 介面直接傳遞的非常重要的資料,比如:使用者的銀行卡號、轉賬金額、使用者身份證等,如果將這些引數,直接明文,暴露到公網上是非常危險的事情。

由此,我們需要對資料進行加密

目前使用比較多的是用BASE64加解密。

我們可以將所有的資料,安裝一定的規律拼接成一個大的字串,然後在加一個金鑰,拼接到一起。

然後使用 JDK1.8 之後的 Base64 工具類處理,效果如下: java 【加密前的資料】www.baidu.com 【加密後的資料】d3d3LmJhaWR1LmNvbQ==

為了安全性,使用 Base64 可以加密多次。

API 介面的呼叫方在傳遞引數時,body 中只有一個引數 data,它就是 base64 之後的加密資料。

API 介面的閘道器服務,在接收到 data 資料後,根據雙方事先預定的金鑰、加密演算法、加密次數等,進行解密,並且反序列化出引數資料。

3. ip 白名單

為了進一步加強 API 介面的安全性,防止介面的簽名或者加密被破解了,攻擊者可以在自己的伺服器上請求該介面。

需求限制請求ip,增加ip白名單

只有在白名單中的 ip 地址,才能成功請求 API 介面,否則直接返回無訪問許可權。

ip 白名單也可以加在 API 閘道器服務上。

但也要防止公司的內部應用伺服器被攻破,這種情況也可以從內部伺服器上發起 API 介面的請求。

這時候就需要增加 web 防火牆了,比如:ModSecurity 等。

4. 限流

如果你的 API 介面被第三方平臺呼叫了,這就意味著著,呼叫頻率是沒法控制的。

第三方平臺呼叫你的 API 介面時,如果併發量一下子太高,可能會導致你的 API 服務不可用,介面直接掛掉。

由此,必須要對 API 介面做限流

限流方法有三種:

  1. 對請求 ip 做限流:比如同一個 ip,在一分鐘內,對API介面總的請求次數,不能超過 10000 次。
  2. 對請求介面做限流:比如同一個 ip,在一分鐘內,對指定的API介面,請求次數不能超過 2000 次。
  3. 對請求使用者做限流:比如同一個AK/SK使用者,在一分鐘內,對 API 介面總的請求次數,不能超過 10000 次。

我們在實際工作中,可以通過nginxredis或者gateway實現限流的功能。

5. 引數校驗

我們需要對 API 介面做引數校驗,比如:校驗必填欄位是否為空,校驗欄位型別,校驗欄位長度,校驗列舉值等等。

這樣做可以攔截一些無效的請求。

比如在新增資料時,欄位長度超過了資料欄位的最大長度,資料庫會直接報錯。

但這種異常的請求,我們完全可以在 API 介面的前期進行識別,沒有必要走到資料庫儲存資料那一步,浪費系統資源。

有些金額欄位,本來是正數,但如果使用者傳入了負數,萬一介面沒做校驗,可能會導致一些沒必要的損失。

還有些狀態欄位,如果不做校驗,使用者如果傳入了系統中不存在的列舉值,就會導致儲存的資料異常。

由此可見,做引數校驗是非常有必要的。

6. 統一返回值

我之前呼叫過別人的 API 介面,正常返回資料是一種 json 格式,比如: java { "code":0, "message":null, "data":[{"id":123,"name":"abc"}] },

簽名錯誤返回的 json 格式: java { "code":1001, "message":"簽名錯誤", "data":null }

沒有資料許可權返回的 json 格式: { "rt":10, "errorMgt":"沒有許可權", "result":null }

這種是比較坑的做法,返回值中有多種不同格式的返回資料,這樣會導致對接方很難理解。

出現這種情況,可能是 API 閘道器定義了一直返回值結構,業務系統定義了另外一種返回值結構。如果是閘道器異常,則返回閘道器定義的返回值結構,如果是業務系統異常,則返回業務系統的返回值結構。

但這樣會導致 API 接口出現不同的異常時,返回不同的返回值結構,非常不利於介面的維護。

其實這個問題我們可以在設計API閘道器時解決。

業務系統在出現異常時,丟擲業務異常的 RuntimeException,其中有個 message 欄位定義異常資訊。

所有的 API 介面都必須經過 API 閘道器,API 閘道器捕獲該業務異常,然後轉換成統一的異常結構返回,這樣能統一返回值結構。

7. 統一封裝異常

我們的 API 介面需要對異常進行統一處理。

不知道你有沒有遇到過這種場景:有時候在 API 介面中,需要訪問資料庫,但表不存在,或者 sql 語句異常,就會直接把 sql 資訊在 API 介面中直接返回。

返回值中包含了異常堆疊資訊資料庫資訊錯誤程式碼和行數等資訊。

如果直接把這些內容暴露給第三方平臺,是很危險的事情。

有些不法分子,利用介面返回值中的這些資訊,有可能會進行 sql 注入或者直接脫庫,而對我們系統造成一定的損失。

因此非常有必要對 API 介面中的異常做統一處理,把異常轉換成這樣: java { "code":500, "message":"伺服器內部錯誤", "data":null }

返回碼code500,返回資訊message伺服器內部異常

這樣第三方平臺就知道是 API 接口出現了內部問題,但不知道具體原因,他們可以找我們排查問題。

我們可以在內部的日誌檔案中,把堆疊資訊、資料庫資訊、錯誤程式碼行數等資訊,打印出來。

我們可以在gateway中對異常進行攔截,做統一封裝,然後給第三方平臺的是處理後沒有敏感資訊的錯誤資訊。

8. 請求日誌

在第三方平臺請求你的 API 介面時,介面的請求日誌非常重要,通過它可以快速的分析和定位問題。

我們需要把 API 介面的請求 url、請求引數、請求頭、請求方式、響應資料和響應時間等,記錄到日誌檔案中。

最好有traceId,可以通過它串聯整個請求的日誌,過濾多餘的日誌。

當然有些時候,請求日誌不光是你們公司開發人員需要檢視,第三方平臺的使用者也需要能檢視介面的請求日誌。

這時就需要把日誌落地到資料庫,比如:mongodb或者elastic search,然後做一個 UI 頁面,給第三方平臺的使用者開通檢視許可權。這樣他們就能在外網檢視請求日誌了,他們自己也能定位一部分問題。

9. 冪等設計

第三方平臺極有可能在極短的時間內,請求我們介面多次,比如:在 1 秒內請求兩次。有可能是他們業務系統有 bug,或者在做介面呼叫失敗重試,因此我們的 API 介面需要做冪等設計

也就是說要支援在極短的時間內,第三方平臺用相同的引數請求 API 介面多次,第一次請求資料庫會新增資料,但第二次請求以後就不會新增資料,但也會返回成功。

這樣做的目的是不會產生錯誤資料。

我們在日常工作中,可以通過在資料庫中增加唯一索引,或者在redis儲存requestId和請求參來保證介面冪等性。

10. 限制記錄條數

對於對我提供的批量介面,一定要限制請求的記錄條數

如果請求的資料太多,很容易造成 API介面超時等問題,讓 API 介面變得不穩定。

通常情況下,建議一次請求中的引數,最多支援傳入 500 條記錄。

如果使用者傳入多餘 500 條記錄,則介面直接給出提示。

建議這個引數做成可配置的,並且要事先跟第三方平臺協商好,避免上線後產生不必要的問題。

11. 壓測

上線前我們務必要對 API 介面做一下壓力測試,知道各個介面的qps情況。

以便於我們能夠更好的預估,需要部署多少伺服器節點,對於 API 介面的穩定性至關重要。

之前雖說對 API 介面做了限流,但是實際上 API 介面是否能夠達到限制的閥值,這是一個問號,如果不做壓力測試,是有很大風險的。

比如:你 API 介面限流 1 秒只允許 50 次請求,但實際 API 介面只能處理 30 次請求,這樣你的 API 介面也會處理不過來。

12. 非同步處理

一般的 API 介面的邏輯都是同步處理的,請求完之後立刻返回結果。

但有時候,我們的 API 接口裡面的業務邏輯非常複雜,特別是有些批量介面,如果同步處理業務,耗時會非常長。

這種情況下,為了提升 API 介面的效能,我們可以改成非同步處理

在 API 介面中可以傳送一條mq訊息,然後直接返回成功。之後,有個專門的mq消費者去非同步消費該訊息,做業務邏輯處理。

直接非同步處理的介面,第三方平臺有兩種方式獲取到。

第一種方式是:我們回撥第三方平臺的介面,告知他們 API 介面的處理結果,很多支付介面就是這麼玩的。

第二種方式是:第三方平臺通過輪詢呼叫我們另外一個查詢狀態的 API 介面,每隔一段時間查詢一次狀態,傳入的引數是之前的那個 API 介面中的 id 集合。

13. 資料脫敏

有時候第三方平臺呼叫我們 API 介面時,獲取的資料中有一部分是敏感資料,比如:使用者手機號、銀行卡號等等。

這樣資訊如果通過 API 介面直接保留到外網,是非常不安全的,很容易造成使用者隱私資料洩露的問題。

這就需要對部分資料做資料脫敏了。

我們可以在返回的資料中,部分內容用星號代替。

已使用者手機號為例:182****887

這樣即使資料被洩露了,也只洩露了一部分,不法分子拿到這份資料也沒啥用。

14. 完整的 API 文件

說實話,一份完整的 API 介面文件,在雙方做介面對接時,可以減少很多溝通成本,讓對方少走很多彎路。

介面文件中需要包含如下資訊:

  1. 介面地址
  2. 請求方式,比如:post 或 get
  3. 請求引數和欄位介紹
  4. 返回值和欄位介紹
  5. 返回碼和錯誤資訊
  6. 加密或簽名示例
  7. 完整的請求 demo
  8. 額外的說明,比如:開通 ip 白名單。

介面文件中最好能夠統一介面和欄位名稱的命名風格,比如都用駝峰標識命名。

統一欄位的型別和長度,比如:id 欄位用 Long 型別,長度規定 20。status 欄位用 int 型別,長度固定 2 等。

統一時間格式欄位,比如:time 用 String 型別,格式為:yyyy-MM-dd HH:mm:ss。

Reference