在實際工作中,我們需要經常跟第三方平臺打交道,可能會對接第三方平臺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介面做限流
。
限流方法有三種:
- 對請求ip做限流:比如同一個ip,在一分鐘內,對
API介面總的請求次數
,不能超過10000次。 - 對請求介面做限流:比如同一個ip,在一分鐘內,對
指定的API介面
,請求次數不能超過2000次。 - 對請求使用者做限流:比如同一個
AK/SK使用者
,在一分鐘內,對API介面總的請求次數,不能超過10000次。
我們在實際工作中,可以透過nginx
,redis
或者gateway
實現限流的功能。
5. 引數校驗
我們需要對API介面做引數校驗
,比如:校驗必填欄位是否為空,校驗欄位型別,校驗欄位長度,校驗列舉值等等。
這樣做可以攔截一些無效的請求。
比如在新增資料時,欄位長度超過了資料欄位的最大長度,資料庫會直接報錯。
但這種異常的請求,我們完全可以在API介面的前期進行識別,沒有必要走到資料庫儲存資料那一步,浪費系統資源。
有些金額欄位,本來是正數,但如果使用者傳入了負數,萬一介面沒做校驗,可能會導致一些沒必要的損失。
還有些狀態欄位,如果不做校驗,使用者如果傳入了系統中不存在的列舉值,就會導致儲存的資料異常。
由此可見,做引數校驗是非常有必要的。
在Java中校驗資料使用最多的是hiberate
的Validator
框架,它裡面包含了@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 }
返回碼code
是500
,返回資訊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介面文件,在雙方做介面對接時,可以減少很多溝通成本,讓對方少走很多彎路。
介面文件中需要包含如下資訊:
- 介面地址
- 請求方式,比如:post或get
- 請求引數和欄位介紹
- 返回值和欄位介紹
- 返回碼和錯誤資訊
- 加密或簽名示例
- 完整的請求demo
- 額外的說明,比如:開通ip白名單。
介面文件中最好能夠統一介面和欄位名稱的命名風格,比如都用駝峰標識
命名。
介面地址中可以加一個版本號v1,比如:v1/query/getCategory,這樣以後介面有很大的變動,可以非常方便升級版本。
統一欄位的型別和長度,比如:id欄位用Long型別,長度規定20。status欄位用int型別,長度固定2等。
統一時間格式欄位,比如:time用String型別,格式為:yyyy-MM-dd HH:mm:ss。
介面文件中寫明AK/SK和域名,找某某單獨提供等。
網文地址