介面控制器層(Controller層)設計(網文)

love/coder發表於2024-11-19

在實際工作中,我們需要經常跟第三方平臺打交道,可能會對接第三方平臺Controller介面,或者提供Controller介面給第三方平臺呼叫。

那麼問題來了,如果設計一個優雅的Controller介面,能夠滿足:安全性、可重複呼叫、穩定性、好定位問題等多方面需求?

今天跟大家一起聊聊設計Controller介面時,需要注意的一些地方,希望對你會有所幫助。

1. 簽名

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

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

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

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

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

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

問題來了:簽名中為什麼要加時間戳?

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

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

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

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

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

2. 加密

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

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

目前使用比較多的是用RSA

RSA包含了一對:公鑰私鑰

我們以使用者登入密碼為例。

在使用者輸入密碼之後,在前端需要對密碼使用公鑰做加密處理。

公鑰是保留在前端程式碼中的,即使洩露給別人了,也沒關係。

因為使用公鑰加密後的密碼,只能使用後端服務中對應的私鑰才能解密。

而我們私鑰儲存在後端服務的配置中,別人無法獲取到。

因此,使用RSA加密和解密是安全的。

我們可以使用線上工具生成金鑰對:https://tools.ytdevops.com/rsa-key-pair-generator

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介面的前期進行識別,沒有必要走到資料庫儲存資料那一步,浪費系統資源。

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

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

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

在Java中校驗資料使用最多的是hiberateValidator框架,它裡面包含了@Null、@NotEmpty、@Size、@Max、@Min等註解。

用它們校驗資料非常方便。

當然有些日期欄位和列舉欄位,可能需要透過自定義註解的方式實現引數校驗。

6. 統一返回值

我之前呼叫過別人的API介面,正常返回資料是一種json格式,比如:

{
    "code":0,
    "message":null,
    "data":[{"id":123,"name":"abc"}]
},

簽名錯誤返回的json格式:

{
    "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介面中的異常做統一處理,把異常轉換成這樣:

{
    "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介面也會處理不過來。

我們在工作中可以用jmeter或者apache benc對API介面做壓力測試。

12. 非同步處理

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

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

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

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

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

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

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

13. 資料脫敏

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

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

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

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

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

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

14. 完整的介面文件

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

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

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

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

介面地址中可以加一個版本號v1,比如:v1/query/getCategory,這樣以後介面有很大的變動,可以非常方便升級版本。

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

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

介面文件中寫明AK/SK和域名,找某某單獨提供等。

網文地址

相關文章