敖丙:大廠是如何設(shè)計(jì)接口的?我:傻瓜...
本文轉(zhuǎn)載自微信公眾號(hào)「三太子敖丙」,作者三太子敖丙 。轉(zhuǎn)載本文請(qǐng)聯(lián)系三太子敖丙公眾號(hào)。
背景
隨著業(yè)務(wù)的發(fā)展,系統(tǒng)架構(gòu)從單體架構(gòu)變?yōu)槊嫦蚍?wù)架構(gòu),水平分層架構(gòu);再變?yōu)槲⒎?wù)架構(gòu),
服務(wù)網(wǎng)格,服務(wù)與服務(wù)間的交互越來(lái)越復(fù)雜,如何優(yōu)雅的設(shè)計(jì)一個(gè)接口,需要考慮哪些方面?特別是對(duì)公服務(wù)(比如BFF)需要對(duì)外提供公網(wǎng)域名的接口,安全性怎么保證,我整理了我工作以來(lái)一些常見(jiàn)的措施以及具體如何去實(shí)現(xiàn):
數(shù)據(jù)有效性校驗(yàn)
合法性校驗(yàn)包括:常規(guī)性校驗(yàn)以及業(yè)務(wù)校驗(yàn);常規(guī)性校驗(yàn):包括必填字段校驗(yàn),長(zhǎng)度校驗(yàn),類型校驗(yàn),格式校驗(yàn)等;業(yè)務(wù)校驗(yàn):根據(jù)實(shí)際業(yè)務(wù)而定,比如訂單金額不能小于0等;
冪等設(shè)計(jì)
所謂冪等,簡(jiǎn)單地說(shuō),就是對(duì)接口的多次調(diào)用所產(chǎn)生的結(jié)果和調(diào)用一次是一致的。數(shù)據(jù)發(fā)生改變才需要做冪等,有些接口是天然保證冪等性的。
比如查詢接口,有些對(duì)數(shù)據(jù)的修改是一個(gè)常量,并且無(wú)其他記錄和操作,那也可以說(shuō)是具有冪等性的。其他情況下,所有涉及對(duì)數(shù)據(jù)的修改、狀態(tài)的變更就都有必要防止重復(fù)性操作的發(fā)生。通過(guò)間接的實(shí)現(xiàn)接口的冪等性來(lái)防止重復(fù)操作所帶來(lái)的影響。
又比如我們電商比較常見(jiàn)的加減GMV同一個(gè)消息無(wú)論過(guò)來(lái)多少次結(jié)果都應(yīng)該只加減一次,不然會(huì)導(dǎo)致金額錯(cuò)誤甚至造成資損。
請(qǐng)求層面: 多次執(zhí)行的結(jié)果是一致的業(yè)務(wù)層面: 同一個(gè)用戶不重復(fù)下單,商品不超賣,MQ不重復(fù)消費(fèi)
冪等的本質(zhì)是分布式鎖的問(wèn)題,分布式鎖正常可以通過(guò)redis或zookeeper實(shí)現(xiàn);
在分布式環(huán)境下,鎖定全局唯一資源,使請(qǐng)求串行化,實(shí)際表現(xiàn)為互斥鎖,防止重復(fù),解決冪等
安全性
1. 數(shù)據(jù)加密
我們知道數(shù)據(jù)在傳輸過(guò)程中是很容易被抓包的,如果直接傳輸比如http協(xié)議傳輸,那么數(shù)據(jù)在傳輸?shù)倪^(guò)程中可能被任何人獲取。
所以必須對(duì)數(shù)據(jù)進(jìn)行加密,常見(jiàn)的做法是對(duì)敏感數(shù)據(jù)比如身份證號(hào)進(jìn)行md5加密。現(xiàn)在主流的做法是使用https協(xié)議,在http和tcp之間添加一層數(shù)數(shù)據(jù)安全層(SSL層),這一層負(fù)責(zé)數(shù)據(jù)的加密和解密。https如何配置和使用,大家翻閱我歷史文章自行去研究。
對(duì)稱加密: 密鑰在加密過(guò)程中和解密過(guò)程中是不變的,常見(jiàn)的算法有DES,AES;優(yōu)點(diǎn)是加解密計(jì)算速度快;缺點(diǎn)是數(shù)據(jù)傳送前,服務(wù)雙方必須約定好密鑰,如果一方密鑰泄露,加密信息也就不安全了。
非對(duì)稱加密: 密鑰成對(duì)出現(xiàn),一個(gè)密鑰加密之后,由另外一個(gè)密鑰來(lái)解密;私鑰放在服務(wù)端文件中,公鑰可以發(fā)布給任何人使用;優(yōu)點(diǎn)是比對(duì)稱加密更安全,但是加解密的速度比對(duì)稱加密慢多了,廣泛使用的是RSA算法;
https的實(shí)現(xiàn)正好是結(jié)合了兩種加密方式,整合了雙方的優(yōu)點(diǎn),在安全性和性能方面都比較好。對(duì)稱加密和非對(duì)稱加密的代碼實(shí)現(xiàn),jdk提供了相關(guān)的工具類可以直接使用,本文不過(guò)多介紹。
2. 數(shù)據(jù)簽名
介紹3種數(shù)據(jù)簽名安全策略:摘要[KEY] , 簽名[證書] , 簽名+加密[證書]
安全策略 | 描述 | 安全級(jí)別 |
---|---|---|
摘要[Key] | 將數(shù)據(jù)和Key(自定義契約密碼)組合后進(jìn)行摘要 | 安全級(jí)別低,契約密鑰安全性非常低。在契約密鑰安全情況下能基本保障數(shù)據(jù)的不可篡改性。 |
簽名[證書] | 使用證書和非對(duì)稱簽名算法對(duì)數(shù)據(jù)進(jìn)行簽名 | 安全級(jí)別中,能夠保障數(shù)據(jù)的不可篡改性和不可抵賴性,但是不能保障數(shù)據(jù)的私密性 |
簽名-加密[證書] | 使用證書和非對(duì)稱算法對(duì)數(shù)據(jù)簽名,使用一次一密的密鑰和對(duì)稱算法對(duì)數(shù)據(jù)進(jìn)行加密 | 安全級(jí)別高,能夠保障數(shù)據(jù)的不可篡改性和不可抵賴性,而且能保障數(shù)據(jù)的私密性。 |
- 機(jī)密性(Confidentiality): 未經(jīng)許可不許看
- 完整性(Integrity) : 不許篡改
- 可用性(Availability) : 防止不可用
- 不可抵賴性(Non-Repudiation): 用戶不能否認(rèn)其行為
摘要[KEY]過(guò)程:將需要提交的數(shù)據(jù)通過(guò)某種方式組合成一個(gè)字符串,然后通過(guò)md5生成一段加密字符串,這段字符串就是數(shù)據(jù)包的簽名,比如:
- str:參數(shù)1={參數(shù)1}&參數(shù)2={參數(shù)2}&……&參數(shù)n={參數(shù)n}$key={用戶密鑰};
- MD5.encrypt(str);
摘要[KEY]原理:Hash算法不可逆,并且計(jì)算結(jié)果具有唯一性,在key 的隱私得到保證的情況下,可以保證完整性摘要[KEY]缺陷:key的隱私性很難保證,明文傳輸
簽名[證書]過(guò)程:客戶端對(duì)明文做一個(gè)md5/SHA計(jì)算,對(duì)計(jì)算后的值通過(guò)私鑰加密得到密文,客戶端將明文和密文發(fā)送給服務(wù)端,服務(wù)端對(duì)密文通過(guò)公鑰解密得到值A(chǔ),同時(shí)服務(wù)端對(duì)明文做一個(gè)md5/SHA計(jì)算得到值B,比較值A(chǔ)與值B,相同得驗(yàn)證通過(guò),能夠保障不可篡性和不可抵賴性,但是不能保障數(shù)據(jù)的私密性(明文傳輸)
簽名+加密[證書]過(guò)程:客戶端生成一個(gè)隨機(jī)字符串,作為password,然后把這個(gè)password通過(guò)B公鑰加密生成密文C,把A明文通過(guò)password加密生成密文B, 同時(shí)把A明文做MD5/SHA計(jì)算后的值通過(guò)A私鑰加密得到簽名D, 把密文B和密文C和簽名D發(fā)給服務(wù)端,服務(wù)端通過(guò)私鑰解密文C得到password,然后通過(guò)password解密文B就可以得到A明文,同時(shí)簽名可以用來(lái)驗(yàn)證發(fā)送者是不是A,以及A發(fā)送的數(shù)據(jù)有沒(méi)有被第三方修改過(guò)。
可以假設(shè)存在一個(gè)惡意的一方X,冒充了A,發(fā)送了密文B(password生成),密文C服務(wù)端收到數(shù)據(jù)后,仍然可以正常解密得到明文,但是卻無(wú)法證明這個(gè)明文數(shù)據(jù)是A發(fā)送的還是惡意用戶B發(fā)送的。簽名D的含義就是A自己簽名,服務(wù)端可以驗(yàn)證。X由于沒(méi)有A的私鑰,這個(gè)簽名它無(wú)法冒充,會(huì)被服務(wù)端識(shí)別出來(lái)。
加密-簽名
3. 時(shí)間戳機(jī)制
數(shù)據(jù)經(jīng)過(guò)了加密處理,酒店抓取到了數(shù)據(jù)也看不到真實(shí)數(shù)據(jù);但是有不法者不關(guān)心真實(shí)數(shù)據(jù),拿到數(shù)據(jù)后直接進(jìn)行惡意請(qǐng)求,這個(gè)時(shí)候簡(jiǎn)單的做法可以考慮時(shí)間戳機(jī)制,在每次請(qǐng)求中加入當(dāng)前時(shí)間,服務(wù)端會(huì)將報(bào)文中的時(shí)間與系統(tǒng)當(dāng)前時(shí)間做比對(duì),看是否在一個(gè)固定的時(shí)間范圍內(nèi)比如5分鐘,惡意偽造的數(shù)據(jù)是沒(méi)法更改報(bào)文中時(shí)間的,超過(guò)5分鐘就可以當(dāng)作非法請(qǐng)求了。
偽代碼如下:
- long interval=5*60*1000;//超時(shí)時(shí)間
- long clientTime=request.getparameter("clientTime");
- long serverTime=System.currentTimeMillis();
- if(serverTime-clientTime>interval){
- return new Response("超過(guò)處理時(shí)長(zhǎng)")
- }
4. AppId機(jī)制
大部分網(wǎng)站需要用戶名和密碼才能登陸,這其實(shí)是一種安全機(jī)制;對(duì)應(yīng)的服務(wù)也可以使用這一機(jī)制,不是誰(shuí)都可以調(diào)用,調(diào)用服務(wù)前必須先申請(qǐng)開(kāi)通一個(gè)唯一的appid,提供相關(guān)的密鑰,在調(diào)用接口時(shí)需要提供appid+密鑰信息,服務(wù)端會(huì)進(jìn)行驗(yàn)證。
appid使用字母,數(shù)字,特殊符號(hào)等隨機(jī)生成,生成的唯一appid看系統(tǒng)實(shí)際要求是否需要全局唯一;不管是否全局唯一最好有以下屬性:
趨勢(shì)遞增: 這樣在保存數(shù)據(jù)庫(kù)的時(shí)候,索引的性能更好
信息安全: 隨機(jī)生成,不要是連續(xù)的,容易被發(fā)現(xiàn)規(guī)律
關(guān)于全局唯一Id生成的方式常見(jiàn)的有snowflake方式等
snowflake
Xnip2020-11-04_19-31-00
以上示意圖描述了一個(gè)序列號(hào)的二進(jìn)制組成結(jié)構(gòu)。
第一位不用,恒為0,即表示正整數(shù);接下來(lái)的41位表示時(shí)間戳,精確到毫秒。為了節(jié)約空間,可以將此時(shí)間戳定義為距離某個(gè)時(shí)間點(diǎn)所經(jīng)歷的毫秒數(shù)(Java默認(rèn)是1970-01-01 00:00:00)。
再后來(lái)的10位用來(lái)標(biāo)識(shí)工作機(jī)器,如果出現(xiàn)了跨IDC的情況,可以將這10位一分為二,一部分用于標(biāo)識(shí)IDC,一部分用于標(biāo)識(shí)服務(wù)器;最后12位是序列號(hào),自增長(zhǎng)。
snowflake的核心思想是64bit的合理分配,但不必要嚴(yán)格按照上圖所示的分法。如果在機(jī)器較少的情況下,可以適當(dāng)縮短機(jī)器id的長(zhǎng)度,留出來(lái)給序列號(hào)。
5. 黑名單機(jī)制
如果此appid進(jìn)行過(guò)很多非法操作,或者說(shuō)專門有一個(gè)中黑系統(tǒng),經(jīng)過(guò)分析之后直接將此appid列入黑名單,所有請(qǐng)求直接返回錯(cuò)誤碼;
我們可以給每個(gè)appid設(shè)置一個(gè)狀態(tài)比如包括:初始化狀態(tài),正常狀態(tài),中黑狀態(tài),關(guān)閉狀態(tài)等等;或者我們直接通過(guò)分布式配置中心,直接保存黑名單列表,每次檢查是否在列表中即可;
限流機(jī)制
常用的限流算法包括:令牌桶限流,漏桶限流,計(jì)數(shù)器限流;
- 令牌桶限流令牌桶算法的原理是系統(tǒng)以一定速率向桶中放入令牌,填滿了就丟棄令牌;請(qǐng)求來(lái)時(shí)會(huì)先從桶中取出令牌,如果能取到令牌,則可以繼續(xù)完成請(qǐng)求,否則等待或者拒絕服務(wù);令牌桶允許一定程度突發(fā)流量,只要有令牌就可以處理,支持一次拿多個(gè)令牌;
- 漏桶限流漏桶算法的原理是按照固定常量速率流出請(qǐng)求,流入請(qǐng)求速率任意,當(dāng)請(qǐng)求數(shù)超過(guò)桶的容量時(shí),新的請(qǐng)求等待或者拒絕服務(wù);可以看出漏桶算法可以強(qiáng)制限制數(shù)據(jù)的傳輸速度;
- 計(jì)數(shù)器限流計(jì)數(shù)器是一種比較簡(jiǎn)單粗暴的算法,主要用來(lái)限制總并發(fā)數(shù),比如數(shù)據(jù)庫(kù)連接池、線程池、秒殺的并發(fā)數(shù);計(jì)數(shù)器限流只要一定時(shí)間內(nèi)的總請(qǐng)求數(shù)超過(guò)設(shè)定的閥值則進(jìn)行限流;
具體基于以上算法如何實(shí)現(xiàn),Guava提供了RateLimiter工具類基于基于令牌桶算法:
- RateLimiter rateLimiter = RateLimiter.create(5);
以上代碼表示一秒鐘只允許處理五個(gè)并發(fā)請(qǐng)求,以上方式只能用在單應(yīng)用的請(qǐng)求限流,不能進(jìn)行全局限流;這個(gè)時(shí)候就需要分布式限流,可以基于redis+lua來(lái)實(shí)現(xiàn);
總結(jié)
其實(shí)接口不管是設(shè)計(jì)還是開(kāi)發(fā),如果不是特別急的需求大家都可以多一點(diǎn)思考,這樣你的系統(tǒng)才會(huì)更穩(wěn)定,上線和測(cè)試過(guò)程中bug更少,而且從個(gè)人提升角度來(lái)說(shuō),多思考總是一件好事。
很多時(shí)候大家都在抱怨:哎呀我公司小,我學(xué)校差這種環(huán)境得不到成長(zhǎng)。傻瓜,很多時(shí)候高手也是這樣走過(guò)來(lái)的,不過(guò)一樣的事情每個(gè)人的態(tài)度不一樣,時(shí)間久了結(jié)果也就不一樣了。
好啦,現(xiàn)在大家應(yīng)該都上班了,我熬夜值班還在大促現(xiàn)場(chǎng)(文章周末寫的,現(xiàn)在就寫個(gè)總結(jié)),我是敖丙,你知道的越多,你不知道的越多,我們下期見(jiàn)。