這才叫 API 接口設(shè)計!
大家好,我是魚皮,前段時間在 星球 里看到了一位小伙伴分享的文章《API 接口設(shè)計最佳實踐》,我也讀了一遍,寫的確實好,給大家分享一下,相信對后端開發(fā)的小伙伴會很有幫助。
API 接口設(shè)計
作者:InfoQ Man
Token 設(shè)計
Token 是服務(wù)端生成的一串字符串,以作客戶端進行請求的一個令牌,當?shù)谝淮蔚卿浐螅?wù)器生成一個 Token 便將此 Token 返回給客戶端,以后客戶端只需帶上這個 Token 前來請求數(shù)據(jù)即可,無需再次帶上用戶名和密碼。
Token 的值一般用 UUID(算法比較著名的有雪花算法),當服務(wù)端接收到客戶端請求后會生成 Token(一串字符,如 etye0fgkgk4ca2ttdsl0ae9a5dd77471fgf),然后將 Token 作為 key 將一些和 Token 關(guān)聯(lián)的信息作為 value 保存到如 Redis 緩存數(shù)據(jù)庫中,同步把該 Token 返回給客戶端;后續(xù)該客戶端的請求都需要帶上這個 Token,服務(wù)器收到請求后就會去緩存服務(wù)器中匹配這個 Token 是否存在,存在則調(diào)用接口,不存在返回接口錯誤。
Token 種類
API Token(接口令牌): ?一般用于訪問不需要用戶登錄的接口,如登錄、注冊、一些基本數(shù)據(jù)的獲取(如信用卡官網(wǎng)的如信用卡費率相關(guān)信息)等。獲取接口令牌需要拿 appId、timestamp 和 sign 來換;其中該 sign 值一般是把 timestamp、key 和對應(yīng)的參數(shù)先進行字母排序再進行 MD5 加密(有時候會加鹽),即 sign=MD5(排序(timestamp+key+參數(shù)));
假設(shè) API 的請求參數(shù)為 channel:T,discount:90%,quantities:10,根據(jù)參數(shù)名稱的 ASCII 碼表的順序排序即為:channel:T,discount:90%,quantities:10。 接著把排序后的參數(shù)名和參數(shù)值拼裝在一起為:channelTdiscount90%quantities10。 把拼裝好的字符串采用 utf-8 編碼,使用簽名算法對編碼后的字節(jié)流進行摘要,即為 sign=md5(channelTdiscount90%quantities10); 最后,Token=hex(appid+sign+timestamp+salt),即可獲得十六進制的一串字符,如“68656C6C6F776F726C64”。
USER Token(用戶令牌): 用于訪問需要用戶登錄之后的接口,如:獲取我的基本信息、保存、修改、刪除等操作。獲取用戶令牌需要拿用戶名和密碼來換。
API 接口設(shè)計原則
1、明確協(xié)議規(guī)范
在設(shè)計初期需要明確雙方的通訊協(xié)議是 TCP、HTTP、RPC,一般針對比較敏感的交易或者行業(yè)(如金融業(yè)),建議使用 HTTPS 協(xié)議以確保數(shù)據(jù)交互的安全。
2、統(tǒng)一接口路徑規(guī)范
建議采用 Restful 的風(fēng)格,一般采用這樣的格式:控制器名/方法名。具體請參考以下例子:
POST /recommend/cardlist
3、統(tǒng)一接口版本管理
APP 后臺邏輯總是處于變化當中,但是 APP 端(如安卓和 ios)因為涉及到應(yīng)用市場的審核問題,還有這些 2C 端的 APP 應(yīng)用存在版本碎片化的問題,因此后臺暴露的接口需要在一段時間內(nèi)支持不同版本的接口,一般方法是通過 Nginx 通過配置過濾根據(jù)接口的不同版本進行路由分發(fā)。
一般來說,接口的版本管理一般有以下兩種方法:
在 URL 中加入 version 信息,如下述; 在 HTTP header 加入 version 信息,這樣就等于只有一個接口,但是具體的不同版本的業(yè)務(wù)邏輯由后臺區(qū)分處理。
POST v1/recommend/cardlist
Nginx的路由分發(fā):
server?{
????listen?80;
????server_name?vip.com;
????location?/v1/?{
????????proxy_pass?http://129.0.0.1:8001/;
????????proxy_redirect?http://129.0.0.1:8001/???/v1/;
????????proxy*set*header?Host?$host;
????}
????location?/v2/?{
????????proxy_pass?http://129.0.0.1:8002/;
????????proxy_redirect?http://129.0.0.1:8002/???/v2/;
????????proxy*set*header?Host?$host;
????}
}
?
server?{
????listen?8001;
????allow?129.0.0.1;??
????deney?all;
????server_name?vip.com;
????root?vip.com/v1/;
}
?
server?{
????listen?8002;
????allow?129.0.0.1;??
????deney?all;
????server_name?vip.com;
????root?vip.com/v2/;
}
4、為你的接口設(shè)定調(diào)用門檻
為調(diào)用你的系統(tǒng)分配一個 ID 和 key,針對每個請求對 ID 和 key 進行校驗,避免在企業(yè)內(nèi)網(wǎng)中的其他系統(tǒng)只要知道接口被可以隨意調(diào)用。
5、接口返回規(guī)范
返回數(shù)據(jù)盡量統(tǒng)一規(guī)范,務(wù)必包括:返回碼、返回信息、數(shù)據(jù)。
{
"code" : 0,
"content" : "string", ?<- 這里為 JSON
"message" : "string"
}
6、接口安全規(guī)范
當我們開發(fā)的接口需要暴露到公網(wǎng),這樣的風(fēng)險跟我們在企業(yè)內(nèi)網(wǎng)暴露給其他系統(tǒng)調(diào)用的風(fēng)險是不可同日而語的。其中有很多風(fēng)險需要我們一一解決。以下僅提供能想到的:
6.1.數(shù)據(jù)如何防止被看到?
目前業(yè)界老生常談就是對稱加密和非對稱加密。
對稱加密:對稱密鑰在加密和解密的過程中使用的密鑰是相同的,常見的對稱加密算法有 DES,AES;優(yōu)點是計算速度快,缺點是在數(shù)據(jù)傳送前,發(fā)送方和接收方必須商定好秘鑰,然后使雙方都能保存好秘鑰,如果一方的秘鑰被泄露,那么加密信息也就不安全了;
非對稱加密:服務(wù)端會生成一對密鑰,私鑰存放在服務(wù)器端,公鑰可以發(fā)布給任何人使用;優(yōu)點就是比起對稱加密更加安全,但是加解密的速度比對稱加密慢太多了;廣泛使用的是 RSA 算法;
目前主流的做法是在傳輸層使用 https 協(xié)議,http 和 tcp 之間添加一層加密層(SSL 層),這一層負責(zé)數(shù)據(jù)的加密和解密。https 協(xié)議則是巧妙的利用上述兩種對稱加密方法;淺顯一點說就是客戶端和服務(wù)端建立三次握手連接過程中通過交換雙方非對稱公鑰,接著使用對方非對稱公鑰加密雙方約定好的對稱密鑰,這樣就只有雙方有這個對稱密鑰(這樣的非對稱加密可以保證很安全的把對稱密鑰給到對方)。后續(xù)雙方的報文溝通就可以使用該對稱密鑰進行加解密(這樣的對稱加密可以保證請求報文可以快速被解密處理,并在處理后被快速加密響應(yīng)回去)。
6.2.數(shù)據(jù)如何防止給篡改?
這個時候我們需要對數(shù)據(jù)進行加簽,數(shù)據(jù)簽名平時用得比較多的是 MD5,即將需要提交的數(shù)據(jù)通過某種方式組合和一個字符串,然后通過 MD5 生成一段加密字符串,這段加密字符串就是數(shù)據(jù)包的簽名。具體請看以下的圖。

6.3.時間戳機制
如果加密數(shù)據(jù)被抓包后被用于重放攻擊,我們怎么辦?這個時候我們可以把解密后的 URL 參數(shù)中的時間戳與系統(tǒng)時間進行比較,如果時間差超過一定間距(如 5 分鐘)即認為該報文被劫持并返回錯誤。但是,務(wù)必保證該時間戳的超時時間一定要跟 sign 保存的有效時間一致。
客戶端在第一次訪問服務(wù)端時,服務(wù)端將 sign 緩存到 Redis 中并把有效時間設(shè)定為跟時間戳的超時時間一致;如果有人使用同一個 URL 再次訪問,如果發(fā)現(xiàn)緩存服務(wù)器中已經(jīng)存在了本次的 sign,則拒絕服務(wù);如果在 Redis 中的 sign 失效的情況下,有人使用同一個 URL 再次訪問,則會被時間戳超時機制攔截。這樣的話,就可以避免 URL 被別人截獲后的重放攻擊。
整個流程如下:
1、客戶端通過用戶名密碼登錄服務(wù)器并獲取 Token
2、客戶端生成時間戳 timestamp,并將 timestamp 作為其中一個參數(shù)
3、客戶端將所有的參數(shù),包括 Token 和 timestamp 按照自己的算法進行排序加密得到簽名 sign
4、將 token、timestamp 和 sign 作為請求時必須攜帶的參數(shù)加在每個請求的 URL 后邊(http://url/request?token=123×tamp=123&sign=123123123)
5、服務(wù)端寫一個過濾器對 token、timestamp 和 sign 進行驗證,只有在 token 有效、timestamp 未超時、緩存服務(wù)器中不存在 sign 三種情況同時滿足,本次請求才有效。
6.4.隨機數(shù)機制
另外,一般會在 URL 參數(shù)上加上隨機數(shù)(即所謂的加鹽)并與 6.3 的時間戳機制組合使用以便提升防重復(fù)提交攻擊。
6.5.黑名單機制
針對同一個 IP 在短時間內(nèi)頻繁請求的,可以通過 Nginx 進行過濾,同步可以在 Nginx 部署動態(tài)黑名單(即 IP 實時更新到黑名單庫),這樣可以防控少量的 DDOS。但受限于判斷黑名單需要考慮多維度的信息,一般我們的 Nginx 盡量只做同一 IP 校驗,更多維度的黑名單校驗可以通過廠商去解決。
6.6.數(shù)據(jù)合法性校驗
這里的數(shù)據(jù)合法性校驗主要指的是數(shù)據(jù)格式校驗和業(yè)務(wù)規(guī)則校驗。
數(shù)據(jù)格式校驗:日期格式校驗、長度校驗、非空校驗等; 業(yè)務(wù)規(guī)則校驗:如庫存校驗、身份證合法性校驗等。
7、冪等性
定義:在計算機中,表示對同一個過程應(yīng)用相同的參數(shù)多次和應(yīng)用一次產(chǎn)生的效果是一樣,這樣的過程即被稱為滿足冪等性。
具體的解決方案有 token 機制、分布式鎖、狀態(tài)機等方案;這里引用一下之前看到的一篇文章,寫得比較詳細:https://blog.csdn.net/u011635492/article/details/81058153
8、接口設(shè)計的一些最佳實踐
即使返回的 JSON 中某字段沒有值(即空值),也一定要返回該字段。同時前端也需做好這類情況的容錯處理; 針對單頁面的多接口請求,為避免擴大攻擊面,建議把多接口邏輯整合到一個接口,一個頁面直接調(diào)用該接口,以避免繞過部分接口進行攻擊; 接口最好支持分頁;分頁一般有電梯式分頁(即一開始算好總頁數(shù),優(yōu)劣也一目了然)和游標式分頁(即每次查詢會拿上一頁的最大的那個 ID 即 cursor 進行查詢,這種方式更適合類似以時間為排序條件的互聯(lián)網(wǎng)單頁應(yīng)用); 針對你的接口提前做好限流;一般常用的限流有計數(shù)器、令牌桶、漏桶這三種。具體請參考接口中的幾種?限流實現(xiàn)。
API 接口管理
一家公司的每個系統(tǒng)都會有各種各樣的接口,但是大部分公司,特別是傳統(tǒng)行業(yè)的公司的所謂接口文檔更多是當每個系傳統(tǒng)的 word 文本格式,這種傳統(tǒng)的格式有著人盡皆知的痛點:
維護不及時; 與代碼不同步; 歸檔后“便束之高閣”; 接口文檔跟代碼沒有互動; 文本檢索無法建立全局搜索,需要額外借助工具。
為了解決上述的問題,需要建立一套行之有效的接口管理體系,該體系的目標是:
能夠進行接口文檔管理,作為后續(xù)的接口治理的其中一部分; 能作為接口測試的平臺,這樣能保證接口跟代碼是同步的; 支持文本檢索。
業(yè)界有很多不同的 API 接口管理平臺,如去哪兒網(wǎng)的 YAPI 平臺、阿里某團隊開發(fā)的 RAP 平臺、Swagger、easyAPI 等等。各位小伙伴可以分享一下自己對這些平臺的使用感受~

最后,歡迎大家加入魚皮的 編程學(xué)習(xí)圈子 ,和 3000 多名 小伙伴們一起交流學(xué)習(xí),向大廠大佬們 1 對 1 提問、跟著魚皮直播做項目(第二期回放已發(fā)布)~
往期推薦
