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

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

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