接口安全11招,真香!
前言
最近知識(shí)星球中有位小伙伴問了我一個(gè)問題:如何保證接口的安全性?
根據(jù)我多年的工作經(jīng)驗(yàn),這篇文章從11個(gè)方面給大家介紹一下保證接口安全的一些小技巧,希望對(duì)你會(huì)有所幫助。
圖片
1 參數(shù)校驗(yàn)
保證接口安全的第一步,也是最重要的一步,需要對(duì)接口的請(qǐng)求參數(shù)做校驗(yàn)。
如果我們把接口請(qǐng)求參數(shù)的校驗(yàn)做好了,真的可以攔截大部分的無(wú)效請(qǐng)求。
我們可以按如下步驟做校驗(yàn):
- 校驗(yàn)參數(shù)是否為空,有些接口中可能會(huì)包含多個(gè)參數(shù),有些參數(shù)允許為空,有些參數(shù)不允許為空,我們需要對(duì)這些參數(shù)做校驗(yàn),防止接口底層出現(xiàn)異常。
- 校驗(yàn)參數(shù)類型,比如:age是int類型的,用戶傳入了一個(gè)字符串:"123abc",這種情況參數(shù)不合法,需要被攔截。
- 校驗(yàn)參數(shù)的長(zhǎng)度,特別是對(duì)于新增或者修改數(shù)據(jù)接口,必須要做參數(shù)長(zhǎng)度的校驗(yàn),否則超長(zhǎng)了數(shù)據(jù)庫(kù)會(huì)報(bào)異常。比如:數(shù)據(jù)庫(kù)username字段長(zhǎng)度是30,新用戶注冊(cè)時(shí),輸入了超過30個(gè)字符的名稱,需要提示用戶名稱超長(zhǎng)了。雖說(shuō)前端會(huì)校驗(yàn)字段長(zhǎng)度,但接口對(duì)參數(shù)長(zhǎng)度的校驗(yàn)也必不可少。
- 校驗(yàn)枚舉值,有些接口參數(shù)是枚舉,比如:status,數(shù)據(jù)庫(kù)中設(shè)計(jì)的該字段只有1、2、3三個(gè)值。如果用戶傳入了4,則需要提示用戶參數(shù)錯(cuò)誤。
- 校驗(yàn)數(shù)據(jù)范圍,對(duì)于有些金額參數(shù),需要校驗(yàn)數(shù)據(jù)范圍,比如:?jiǎn)喂P交易的money必須大于0,小于10000。
我們可以自己寫代碼,對(duì)每個(gè)接口的請(qǐng)求參數(shù)一一做校驗(yàn)。
也可以使用一些第三方的校驗(yàn)框架。
比如:hiberate的Validator框架,它里面包含了@Null、@NotEmpty、@Size、@Max、@Min等注解。
用它們校驗(yàn)數(shù)據(jù)非常方便。
當(dāng)然有些日期字段和枚舉字段,可能需要通過自定義注解的方式實(shí)現(xiàn)參數(shù)校驗(yàn)。
2 統(tǒng)一封裝返回值
可能有些小伙伴認(rèn)為,對(duì)接口返回值統(tǒng)一封裝是為了讓代碼更規(guī)范。
其實(shí)也是處于安全方面的考慮。
假如有這樣一種場(chǎng)景:你寫的某個(gè)接口底層的sql,在某種條件下有語(yǔ)法問題。某個(gè)用戶請(qǐng)求接口之后,在訪問數(shù)據(jù)庫(kù)時(shí),直接報(bào)了sql語(yǔ)法錯(cuò)誤,將數(shù)據(jù)庫(kù)名、表名、字段名、相關(guān)sql語(yǔ)句都打印出來(lái)了。
此時(shí),如果你的接口將這些異常信息直接返回給外網(wǎng)的用戶,有些黑客拿著這些信息,將參數(shù)做一些調(diào)整,拼接一些注入sql,可以對(duì)你的數(shù)據(jù)庫(kù)發(fā)起攻擊。
因此,非常有必要對(duì)接口的返回值做統(tǒng)一的封裝。
例如下面這樣:
{
"code":0,
"message":null,
"data":[{"id":123,"name":"abc"}]
}
該json返回值中定義了三個(gè)字段:
- code:表示響應(yīng)碼,0-成功,1-參數(shù)為空,2-參數(shù)錯(cuò)誤,3-簽名錯(cuò)誤 4-請(qǐng)求超時(shí) 5-服務(wù)器內(nèi)部錯(cuò)誤等。
- message:表示提示信息,如果請(qǐng)求成功,則返回空。如果請(qǐng)求失敗,則返回我們專門在代碼中處理過,讓用戶能看懂的錯(cuò)誤信息。
- data:表示具體的數(shù)據(jù),返回的是一個(gè)json字段。
對(duì)返回值這樣封裝之后,即使在接口的底層出現(xiàn)了數(shù)據(jù)庫(kù)的異常,也不會(huì)直接提示用戶,給用戶提示的是服務(wù)器內(nèi)部錯(cuò)誤。
對(duì)返回值統(tǒng)一封裝的工作,沒有必要在業(yè)務(wù)代碼中做,完全可以在放到API網(wǎng)關(guān)。
業(yè)務(wù)系統(tǒng)在出現(xiàn)異常時(shí),拋出業(yè)務(wù)異常的RuntimeException,其中有個(gè)message字段定義異常信息。
所有的API接口都必須經(jīng)過API網(wǎng)關(guān),API網(wǎng)關(guān)捕獲該業(yè)務(wù)異常,然后轉(zhuǎn)換成統(tǒng)一的異常結(jié)構(gòu)返回,這樣能統(tǒng)一返回值結(jié)構(gòu)。
3 做轉(zhuǎn)義
在用戶自定義輸入框,用戶可以輸入任意內(nèi)容。
有些地方需要用html的格式顯示用戶輸入的內(nèi)容,比如文章詳情頁(yè)或者合同詳情頁(yè),用戶可以自定義文案和樣式。
這些地方如果我們不做處理,可能會(huì)遭受XSS(Cross Site Scripting)攻擊,也就是跨站腳本攻擊。
攻擊者可以在輸入的內(nèi)容中,增加腳本,比如:<script>alert("反射型 XSS 攻擊")</script>,這樣在訪問合同詳情頁(yè)時(shí),會(huì)彈出一個(gè)不需要的窗口,攻擊者甚至可以引導(dǎo)用戶訪問一些惡意的鏈接。
由此,我們需要對(duì)用戶輸入內(nèi)容中的一些特殊標(biāo)簽做轉(zhuǎn)義。
下面這張圖中列出了需要轉(zhuǎn)義的常見字符和轉(zhuǎn)義后的字符:
圖片
我們可以自定義一個(gè)轉(zhuǎn)義注解,打上該注解的字段,表示需要轉(zhuǎn)義。
有個(gè)專門的AOP攔截器,將用戶的原始內(nèi)容,轉(zhuǎn)換成轉(zhuǎn)義后的內(nèi)容。
保存到數(shù)據(jù)庫(kù)中是轉(zhuǎn)義之后的內(nèi)容。
除此之外,為了防止SQL注入的情況,也需要將用戶輸入的參數(shù)做SQL語(yǔ)句方面的轉(zhuǎn)義。
關(guān)于SQL注入問題,可以參考我之前寫的這篇文章:臥槽,sql注入竟然把我們的系統(tǒng)搞掛了
4 做權(quán)限控制
我們需要對(duì)接口做權(quán)限控制。
主要包含了下面3種情況。
4.1 校驗(yàn)是否登錄
對(duì)于用些公共數(shù)據(jù),比如:外部分類,所有人都能夠看到,不用登錄也能看到。
對(duì)于這種接口,則不用校驗(yàn)登錄。
而對(duì)于有些查看內(nèi)部分類的接口,需要用戶登錄之后,才能訪問。
這種情況就需要校驗(yàn)登錄了。
可以從當(dāng)前用戶上下文中獲取用戶信息,校驗(yàn)用戶是否登錄。
如果用戶登錄了,當(dāng)前用戶上下文中該用戶的信息不為空。
否則,如果用戶沒登錄,則當(dāng)前用戶上下文中該用戶的信息為空。
4.2 接口功能權(quán)限控制
對(duì)于有些重要的接口,比如訂單審核接口,只有擁有訂單審核權(quán)限的運(yùn)營(yíng)賬號(hào),才有權(quán)限訪問該接口。
我們需要對(duì)該接口做功能權(quán)限控制。
可以自定義一個(gè)權(quán)限注解,在注解上可以添加權(quán)限點(diǎn)。
在網(wǎng)關(guān)層有個(gè)攔截器,會(huì)根據(jù)當(dāng)前請(qǐng)求的用戶的權(quán)限,去跟請(qǐng)求的接口的權(quán)限做匹配,只有匹配上次允許訪問該接口。
4.3 接口數(shù)據(jù)權(quán)限控制
對(duì)于有些訂單查詢接口,普通運(yùn)營(yíng)只能查看普通用戶的數(shù)據(jù)。
而運(yùn)營(yíng)經(jīng)理可以查看普通用戶和vip用戶的數(shù)據(jù)。
這種情況我們需要對(duì)該訂單查詢接口做數(shù)據(jù)權(quán)限控制。
不同的角色,能夠查看的數(shù)據(jù)范圍不同。
可以在查詢數(shù)據(jù)時(shí),在sql語(yǔ)句中動(dòng)態(tài)拼接過濾數(shù)據(jù)權(quán)限的sql。
5 加驗(yàn)證碼
對(duì)于一些非常重要的接口,在做接口設(shè)計(jì)的時(shí)候,要考慮惡意用戶刷接口的情況。
最早的用戶注冊(cè)接口,是需要用圖形驗(yàn)證碼校驗(yàn)的,比如下面這樣的:
圖片
用戶只需要輸入:賬號(hào)名稱、密碼和驗(yàn)證碼即可,完成注冊(cè)。
其中賬號(hào)名稱作為用戶的唯一標(biāo)識(shí)。
但有些圖形驗(yàn)證碼比較簡(jiǎn)單,很容易被一些暴力破解工具破解。
由此,要給圖形驗(yàn)證碼增加難道,增加一些干擾項(xiàng),增加暴力破解工具的難道。
但有個(gè)問題是:如果圖形驗(yàn)證碼太復(fù)雜了,會(huì)對(duì)正常用戶使用造成一點(diǎn)的困擾,增加了用戶注冊(cè)的成本,讓用戶注冊(cè)功能的效果會(huì)大打折扣。
因此,僅靠圖形驗(yàn)證碼,防止用戶注冊(cè)接口被刷,難道太大了。
后來(lái),又出現(xiàn)了一種移動(dòng)滑塊形式的圖形驗(yàn)證方式,安全性更高。
圖片
此外,使用驗(yàn)證碼比較多的地方是發(fā)手機(jī)短信的功能。
發(fā)手機(jī)短信的功能,一般是購(gòu)買的云服務(wù)廠商的短信服務(wù),按次收費(fèi),比如:發(fā)一條短信0.1元。
如果發(fā)送短信的接口,不做限制,被用戶惡意調(diào)用,可能會(huì)產(chǎn)生非常昂貴的費(fèi)用。
6 限流
上一節(jié)中提到的發(fā)送短信接口,只校驗(yàn)驗(yàn)證碼還不夠,還需要對(duì)用戶請(qǐng)求做限流。
從頁(yè)面上的驗(yàn)證碼,只能限制當(dāng)前頁(yè)面的不能重復(fù)發(fā)短信,但如果用戶刷新了頁(yè)面,也可以重新發(fā)短信。
因此非常有必要在服務(wù)端,即:發(fā)送短信接口做限制。
我們可以增加一張短信發(fā)送表。
該表包含:id、短信類型、短信內(nèi)容、手機(jī)號(hào)、發(fā)送時(shí)間等字段。
圖片
有用戶發(fā)送短信請(qǐng)求過來(lái)時(shí):
- 先查詢?cè)撌謾C(jī)號(hào)最近一次發(fā)送短信的記錄
- 如果沒有發(fā)送過,則發(fā)送短信。
- 如果該手機(jī)號(hào)已經(jīng)發(fā)送過短信,但發(fā)送時(shí)間跟當(dāng)前時(shí)間比超過了60秒,則重新發(fā)送一條新的短信。
- 如果發(fā)送時(shí)間跟當(dāng)前時(shí)間比沒超過60秒,則直接提示用戶操作太頻繁,請(qǐng)稍后重試。
這樣就能非常有效的防止惡意用戶刷短信的行為。
但還是有漏洞。
比如:用戶知道在60秒以內(nèi),是沒法重復(fù)發(fā)短信的。他有個(gè)程序,剛好每隔60秒發(fā)一條短信。
這樣1個(gè)手機(jī)號(hào)在一天內(nèi)可以發(fā):60*24 = 1440 條短信。
如果他有100個(gè)手機(jī)號(hào),那么一天也可以刷你很多條短信。
由此,還需要限制每天同一個(gè)手機(jī)號(hào)可以發(fā)的短信次數(shù)。
其實(shí)可以用redis來(lái)做。
用戶發(fā)短信之后,在redis中保存一條記錄,key是手機(jī)號(hào),value是發(fā)短信的次數(shù),過期時(shí)間是24小時(shí)。
這樣在發(fā)送短信之前,要先查詢一下,當(dāng)天發(fā)送短信的次數(shù)是否超過10次(假設(shè)同一個(gè)手機(jī)號(hào)一天最多允許發(fā)10條短信)。
如果超過10次,則直接提示用戶操作太頻繁,請(qǐng)稍后重試。
如果沒超過10次,則發(fā)送短信,并且把redis中該手機(jī)號(hào)對(duì)應(yīng)的value值加1。
短信發(fā)送接口完整的校驗(yàn)流程如下:
圖片
7 加ip白名單
對(duì)于有些非常重要的基礎(chǔ)性的接口,比如:會(huì)員系統(tǒng)的開通會(huì)員接口,業(yè)務(wù)系統(tǒng)可能會(huì)調(diào)用該接口開通會(huì)員。
會(huì)員系統(tǒng)為了安全性考慮,在設(shè)計(jì)開通會(huì)員接口的時(shí)候,可能會(huì)加一個(gè)ip白名單,對(duì)非法的服務(wù)器請(qǐng)求進(jìn)行攔截。
這個(gè)ip白名單前期可以做成一個(gè)Apollo配置,可以動(dòng)態(tài)生效。
如果后期ip數(shù)量多了的話,可以直接保存到數(shù)據(jù)庫(kù)。
只有ip在白名單中的那些服務(wù)器,才允許調(diào)用開通會(huì)員接口。
這樣即使開通會(huì)員接口地址和請(qǐng)求參數(shù)被泄露了,調(diào)用者的ip不在白名單上,請(qǐng)求開通會(huì)員接口會(huì)直接失敗。
除非調(diào)用者登錄到了某一個(gè)白名單ip的對(duì)應(yīng)的服務(wù)器,這種情況極少,因?yàn)橐话氵\(yùn)維會(huì)設(shè)置對(duì)訪問器訪問的防火墻。
當(dāng)然如果用了Fegin這種走內(nèi)部域名的方式訪問接口,可以不用設(shè)置ip白名單,內(nèi)部域名只有在公司的內(nèi)部服務(wù)器之間訪問,外面的用戶根本訪問不了。
但對(duì)于一些第三方平臺(tái)的接口,他們更多的是通過設(shè)置ip白名單的方式保證接口的安全性。
8 校驗(yàn)敏感詞
對(duì)于某些用戶可以自定義內(nèi)容的接口,還需要對(duì)用戶輸入的內(nèi)容做敏感詞校驗(yàn)。
比如:在創(chuàng)建商品頁(yè)面,用戶輸入了:傻逼商品,結(jié)果直接顯示到了商城的商品列表頁(yè)面,這種情況肯定是不被允許的。
當(dāng)然你也可以做一個(gè)審核功能,對(duì)用戶創(chuàng)建的商品信息做人工審核,如果商品數(shù)量太多,這樣會(huì)浪費(fèi)很多人力。
有個(gè)比較好的做法是:對(duì)用戶自定義的內(nèi)容,做敏感詞校驗(yàn)。
可以調(diào)用第三方平臺(tái)的接口,也可以自己實(shí)現(xiàn)一個(gè)敏感詞校驗(yàn)接口。
可以在GitHub上下載一個(gè)開源的敏感詞庫(kù),將那些敏感詞導(dǎo)入到數(shù)據(jù)庫(kù)中。
然后使用hanlp對(duì)用戶輸入的內(nèi)容,進(jìn)行分詞。對(duì)分好的詞,去匹配敏感詞庫(kù)中的那些敏感詞。
如果匹配上了,則說(shuō)明是敏感詞,則驗(yàn)證不通過。
如果沒有匹配上,則說(shuō)明非敏感詞,則驗(yàn)證通過。
不可能在每個(gè)業(yè)務(wù)接口中都調(diào)用敏感詞校驗(yàn)接口,我們可以自定義注解,在AOP攔截器中調(diào)用敏感詞校驗(yàn)接口。
在調(diào)用業(yè)務(wù)接口之前,先觸發(fā)攔截器,校驗(yàn)打了敏感詞校驗(yàn)注解的那些字段,將他們里面包含的內(nèi)容,作為入?yún)魅朊舾性~校驗(yàn)接口做校驗(yàn)。
當(dāng)然有時(shí)候hanlp分詞器會(huì)把句子分錯(cuò)詞,還需要添加一個(gè)敏感詞的白名單,白名單中的詞不是敏感詞。
9 使用https協(xié)議
以前很多接口使用的是HTTP(HyperText Transport Protocol,即超文本傳輸協(xié)議)協(xié)議,它用于傳輸客戶端和服務(wù)器端的數(shù)據(jù)。
雖說(shuō)HTTP使用很簡(jiǎn)單也很方便,但卻存在以下3個(gè)致命問題:
- 使用明文通訊,內(nèi)容容易被竊聽。
- 不驗(yàn)證通訊方的真實(shí)身份,容易遭到偽裝。
- 無(wú)法證明報(bào)文的完整性,報(bào)文很容易被篡改。
為了解決HTTP協(xié)議的這些問題,出現(xiàn)了HTTPS協(xié)議。
HTTPS協(xié)議是在HTTP協(xié)議的基礎(chǔ)上,添加了加密機(jī)制:
- SSL:它是Secure Socket Layer的縮寫, 表示安全套接層。
- TLS:它是Transport Layer Security的縮寫,表示傳輸層安全。
HTTPS = HTTP + 加密 + 認(rèn)證 + 完整性保護(hù)。
為了安全性考慮,我們的接口如果能使用HTTPS協(xié)議,盡量少使用HTTP協(xié)議。
如果你訪問過一些大廠的網(wǎng)站,會(huì)發(fā)現(xiàn)他們提供的接口,都是使用的HTTPS協(xié)議。
不過需要注意的地方是:HTTPS協(xié)議需要申請(qǐng)證書,有些額外的費(fèi)用。
10 數(shù)據(jù)加密
有些信息是用戶的核心信息,比如:手機(jī)號(hào)、郵箱、密碼、身份證、銀行卡號(hào)等,不能別泄露出去。
在保存到數(shù)據(jù)庫(kù)時(shí),我們要將這些字段,做加密處理。
后面即使這些數(shù)據(jù)被泄露了,獲得數(shù)據(jù)的人,由于沒有密鑰,沒辦法解密。
這種情況可以使用AES對(duì)稱加密的方式,因?yàn)楹竺嫦到y(tǒng)的有些業(yè)務(wù)場(chǎng)景,需要把加密的數(shù)據(jù)解密出來(lái)。
為了安全性考慮,我們需要設(shè)置一個(gè)用于加密的密鑰,這個(gè)密鑰可以稍微復(fù)雜一點(diǎn),包含一些數(shù)字、字母和特殊字符。
我們同樣可以通過自定義注解的方式,給需要加密的字段添加該注解,在Mybatis攔截器中實(shí)現(xiàn)加解密的功能。
對(duì)于查詢操作,需要將加了該注解的字段的數(shù)據(jù)做解密處理。
對(duì)于寫入操作,要將加了該注解的字段的數(shù)據(jù)做加密處理。
有些頁(yè)面顯示的地方,手機(jī)號(hào)一般不會(huì)顯示完整的手機(jī)號(hào),中間有一部分用*代替,比如:182***3457。
這種情況需要做特殊處理。
11 做風(fēng)險(xiǎn)控制
有些特殊的接口,比如用戶登錄接口,我們需要對(duì)該接口做風(fēng)險(xiǎn)控制,盡可能減小被盜號(hào)的風(fēng)險(xiǎn)。
用戶登錄失敗之后,需要有地方,比如:Redis,記錄用戶登錄失敗的次數(shù)。
如果用戶第一次輸入賬號(hào)密碼登錄時(shí),出現(xiàn)的是一個(gè)稍微簡(jiǎn)單的驗(yàn)證碼。
如果用戶把賬號(hào)或密碼連續(xù)輸錯(cuò)3次之后,出現(xiàn)了更復(fù)雜的驗(yàn)證碼。
或者改成使用手機(jī)短信驗(yàn)證。
如果用戶在一天之內(nèi),把賬號(hào)或密碼連續(xù)輸錯(cuò)10次,則直接鎖定該賬號(hào)。
這樣處理是為了防止有人用一些軟件,暴力破解賬號(hào)和密碼。
在用戶登錄成功之后,需要有一張表記錄用戶的ip、所在城市和登錄的設(shè)備id。
如果你的賬號(hào)被盜了。
在盜號(hào)者在頁(yè)面輸入賬號(hào)密碼登錄,會(huì)調(diào)用登錄接口,此時(shí)登錄接口中可以根據(jù)用戶的ip和設(shè)備id,做一些風(fēng)險(xiǎn)控制。
接口判斷如果用戶當(dāng)前登錄的ip、所在城市和設(shè)備ip,跟上一次登錄成功時(shí)記錄的相差非常大。
比如:1小時(shí)之前,用的ip是100.101.101.101,城市是北京,設(shè)備id是1001,而1小時(shí)之后,用的ip是200.202.202.101,城市是廣州,設(shè)備id是2002。
這種情況用戶的賬號(hào)極有可能被盜了。
登錄接口做安全性升級(jí),需要校驗(yàn)用戶手機(jī)驗(yàn)證碼才能登錄成功。
由于盜號(hào)者只有你的賬號(hào)和密碼,沒有手機(jī)驗(yàn)證碼,所以即使被盜號(hào)了,也沒辦法登錄成功。