在實際工作中,我們需要經常跟第三方平臺打交道,可能會對接第三方平臺API介面,或者提供API介面給第三方平臺呼叫。
那麼問題來了,如果設計一個優雅的API介面,能夠滿足:安全性、可重複呼叫、穩定性、好定位問題等多方面需求?
今天跟大家一起聊聊設計API介面時,需要注意的一些地方,希望對你會有所幫助。
為了防止API介面中的資料被篡改,很多時候我們需要對API介面做簽名
。
介面請求方將請求引數
+ 時間戳
+ 金鑰
拼接成一個字串,然後通過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。
有些時候,我們的API介面直接傳遞的非常重要的資料,比如:使用者的銀行卡號、轉賬金額、使用者身份證等,如果將這些引數,直接明文,暴露到公網上是非常危險的事情。
由此,我們需要對資料進行加密
。
目前使用比較多的是用BASE64
加解密。
我們可以將所有的資料,安裝一定的規律拼接成一個大的字串,然後在加一個金鑰
,拼接到一起。
然後使用JDK1.8之後的Base64工具類處理,效果如下:
【加密前的資料】www.baidu.com
【加密後的資料】d3d3LmJhaWR1LmNvbQ==
為了安全性,使用Base64可以加密多次。
API介面的呼叫方在傳遞引數時,body中只有一個引數data,它就是base64之後的加密資料。
API介面的閘道器服務,在接收到data資料後,根據雙方事先預定的金鑰、加密演演算法、加密次數等,進行解密,並且反序列化出引數資料。
為了進一步加強API介面的安全性,防止介面的簽名或者加密被破解了,攻擊者可以在自己的伺服器上請求該介面。
需求限制請求ip
,增加ip白名單
。
只有在白名單中的ip地址,才能成功請求API介面,否則直接返回無存取許可權。
ip白名單也可以加在API閘道器服務上。
但也要防止公司的內部應用伺服器被攻破,這種情況也可以從內部伺服器上發起API介面的請求。
這時候就需要增加web防火牆了,比如:ModSecurity等。
如果你的API介面被第三方平臺呼叫了,這就意味著著,呼叫頻率是沒法控制的。
第三方平臺呼叫你的API介面時,如果並行量一下子太高,可能會導致你的API服務不可用,介面直接掛掉。
由此,必須要對API介面做限流
。
限流方法有三種:
API介面總的請求次數
,不能超過10000次。指定的API介面
,請求次數不能超過2000次。AK/SK使用者
,在一分鐘內,對API介面總的請求次數,不能超過10000次。我們在實際工作中,可以通過nginx
,redis
或者gateway
實現限流的功能。
我們需要對API介面做引數校驗
,比如:校驗必填欄位是否為空,校驗欄位型別,校驗欄位長度,校驗列舉值等等。
這樣做可以攔截一些無效的請求。
比如在新增資料時,欄位長度超過了資料欄位的最大長度,資料庫會直接報錯。
但這種異常的請求,我們完全可以在API介面的前期進行識別,沒有必要走到資料庫儲存資料那一步,浪費系統資源。
有些金額欄位,本來是正數,但如果使用者傳入了負數,萬一介面沒做校驗,可能會導致一些沒必要的損失。
還有些狀態列位,如果不做校驗,使用者如果傳入了系統中不存在的列舉值,就會導致儲存的資料異常。
由此可見,做引數校驗是非常有必要的。
在Java中校驗資料使用最多的是hiberate
的Validator
框架,它裡面包含了@Null、@NotEmpty、@Size、@Max、@Min等註解。
用它們校驗資料非常方便。
當然有些日期欄位和列舉欄位,可能需要通過自定義註解的方式實現引數校驗。
我之前呼叫過別人的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閘道器捕獲該業務異常,然後轉換成統一的異常結構返回,這樣能統一返回值結構。
我們的API介面需要對異常
進行統一處理。
不知道你有沒有遇到過這種場景:有時候在API介面中,需要存取資料庫,但表不存在,或者sql語句異常,就會直接把sql資訊在API介面中直接返回。
返回值中包含了異常堆疊資訊
、資料庫資訊
、錯誤程式碼和行數
等資訊。
如果直接把這些內容暴露給第三方平臺,是很危險的事情。
有些不法分子,利用介面返回值中的這些資訊,有可能會進行sql注入或者直接脫庫,而對我們系統造成一定的損失。
因此非常有必要對API介面中的異常做統一處理,把異常轉換成這樣:
{
"code":500,
"message":"伺服器內部錯誤",
"data":null
}
返回碼code
是500
,返回資訊message
是伺服器內部異常
。
這樣第三方平臺就知道是API介面出現了內部問題,但不知道具體原因,他們可以找我們排查問題。
我們可以在內部的紀錄檔檔案中,把堆疊資訊、資料庫資訊、錯誤程式碼行數等資訊,列印出來。
我們可以在gateway
中對異常進行攔截,做統一封裝,然後給第三方平臺的是處理後沒有敏感資訊的錯誤資訊。
在第三方平臺請求你的API介面時,介面的請求紀錄檔非常重要,通過它可以快速的分析和定位問題。
我們需要把API介面的請求url、請求引數、請求頭、請求方式、響應資料和響應時間等,記錄到紀錄檔檔案中。
最好有traceId
,可以通過它串聯整個請求的紀錄檔,過濾多餘的紀錄檔。
當然有些時候,請求紀錄檔不光是你們公司開發人員需要檢視,第三方平臺的使用者也需要能檢視介面的請求紀錄檔。
這時就需要把紀錄檔落地到資料庫,比如:mongodb
或者elastic search
,然後做一個UI頁面,給第三方平臺的使用者開通檢視許可權。這樣他們就能在外網檢視請求紀錄檔了,他們自己也能定位一部分問題。
第三方平臺極有可能在極短的時間內,請求我們介面多次,比如:在1秒內請求兩次。有可能是他們業務系統有bug,或者在做介面呼叫失敗重試,因此我們的API介面需要做冪等設計
。
也就是說要支援在極短的時間內,第三方平臺用相同的引數請求API介面多次,第一次請求資料庫會新增資料,但第二次請求以後就不會新增資料,但也會返回成功。
這樣做的目的是不會產生錯誤資料。
我們在日常工作中,可以通過在資料庫
中增加唯一索引
,或者在redis
儲存requestId
和請求參來保證介面冪等性。
對介面冪等性感興趣的小夥伴,可以看看我的另一篇文章《高並行下如何保證介面的冪等性?》,裡面有非常詳細的介紹。
對於對我提供的批次介面,一定要限制請求的記錄條數
。
如果請求的資料太多,很容易造成API介面超時
等問題,讓API介面變得不穩定。
通常情況下,建議一次請求中的引數,最多支援傳入500條記錄。
如果使用者傳入多餘500條記錄,則介面直接給出提示。
建議這個引數做成可設定的,並且要事先跟第三方平臺協商好,避免上線後產生不必要的問題。
上線前我們務必要對API介面做一下壓力測試
,知道各個介面的qps
情況。
以便於我們能夠更好的預估,需要部署多少伺服器節點,對於API介面的穩定性至關重要。
之前雖說對API介面做了限流,但是實際上API介面是否能夠達到限制的閥值,這是一個問號,如果不做壓力測試,是有很大風險的。
比如:你API介面限流1秒只允許50次請求,但實際API介面只能處理30次請求,這樣你的API介面也會處理不過來。
我們在工作中可以用jmeter
或者apache benc
對API介面做壓力測試。
一般的API介面的邏輯都是同步處理的,請求完之後立刻返回結果。
但有時候,我們的API介面裡面的業務邏輯非常複雜,特別是有些批次介面,如果同步處理業務,耗時會非常長。
這種情況下,為了提升API介面的效能,我們可以改成非同步處理
。
在API介面中可以傳送一條mq訊息
,然後直接返回成功。之後,有個專門的mq消費者
去非同步消費該訊息,做業務邏輯處理。
直接非同步處理的介面,第三方平臺有兩種方式獲取到。
第一種方式是:我們回撥
第三方平臺的介面,告知他們API介面的處理結果,很多支付介面就是這麼玩的。
第二種方式是:第三方平臺通過輪詢
呼叫我們另外一個查詢狀態的API介面,每隔一段時間查詢一次狀態,傳入的引數是之前的那個API介面中的id集合。
有時候第三方平臺呼叫我們API介面時,獲取的資料中有一部分是敏感資料,比如:使用者手機號、銀行卡號等等。
這樣資訊如果通過API介面直接保留到外網,是非常不安全的,很容易造成使用者隱私資料洩露的問題。
這就需要對部分資料做資料脫敏
了。
我們可以在返回的資料中,部分內容用星號
代替。
已使用者手機號為例:182****887
。
這樣即使資料被洩露了,也只洩露了一部分,不法分子拿到這份資料也沒啥用。
說實話,一份完整的API介面檔案,在雙方做介面對接時,可以減少很多溝通成本,讓對方少走很多彎路。
介面檔案中需要包含如下資訊:
介面檔案中最好能夠統一介面和欄位名稱的命名風格,比如都用駝峰標識
命名。
統一欄位的型別和長度,比如:id欄位用Long型別,長度規定20。status欄位用int型別,長度固定2等。
統一時間格式欄位,比如:time用String型別,格式為:yyyy-MM-dd HH:mm:ss。
介面檔案中寫明AK/SK和域名,找某某單獨提供等。
如果這篇文章對您有所幫助,或者有所啟發的話,幫忙掃描下發二維條碼關注一下,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
關注公眾號:【蘇三說技術】,在公眾號中回覆:面試、程式碼神器、開發手冊、時間管理有超讚的粉絲福利,另外回覆:加群,可以跟很多BAT大廠的前輩交流和學習。