- 概述
公司、個人開發的系統上線后,系統中 API 暴露到網絡上會存在一定的安全風險,比如:爬蟲、惡意訪問、錯誤訪問等。API沒有安全性,用戶可以任意注冊即可無限次訪問和調用API,且沒有請求與特定用戶數據關聯的簡單方法,就無法防止惡意用戶的惡意請求等。
常用 API 接口安全措施如下幾種:
(1)數據加密
數據在互聯網傳輸過程很容易被抓包,如果直接傳輸,那么用戶數據可能被其他人獲取,導致系統安全性等問題,所以必須對數據加密。常見的做法是對關鍵字段加密,比如用戶密碼直接通過 md5 加密。
(2)數據簽名
數據在傳輸過程中經過加密,理論上就算被抓包,也無法對數據進行篡改,但是我們一般加密的部分其實只是在外網,現在很多服務在內網中都需要經過很多服務跳轉,如果被攻入內網,則可以在任意節點篡改數據,所以這里的加數據簽名可以防止內網中數據被篡改。數據簽名就是由發送者產生一段無法偽造的一段數字串,來保證數據在傳輸過程中不被篡改。md5算法是常用的數據簽名算法,其原理是將需要提交的數據通過某種方式組合成一個字符串,然后通過md5算法生成一段加密字符串,這段加密字符串就是數據包的簽名。為保證安全性,最后的密鑰會在客戶端和服務端各備一份。
(3)添加時間戳
經過如上的加密,加簽處理,就算拿到數據也不能看到真實的數據;但是有些攻擊者不關心真實的數據,而是直接拿到抓取的數據包做惡意請求,以達到攻擊的目的。我們可以使用時間戳機制,在每次請求的時候加入當前的時間,服務器端會拿到當前時間和消息中的時間相減,看看是否在一個固定的時間范圍內,超過時間差的請求就視為非法請求。
(4)限流機制
如果有用戶出現頻繁調用接口的情況;這種情況需要給相關用戶做限流處理,常用的限流算法包括:令牌桶限流,漏桶限流,計數器限流。令牌桶限流:系統以一定速率向桶中放入令牌,填滿了就丟棄令牌;請求來時會先從桶中取出令牌,如果能取到令牌,則可以繼續完成請求,否則等待或者拒絕服務。令牌桶允許一定程度突發流量,只要有令牌就可以處理,支持一次拿多個令牌。漏桶限流:按照固定常量速率流出請求,流入請求速率任意,當請求數超過桶的容量時,新的請求等待或者拒絕服務,因此漏桶算法可以強制限制數據的傳輸速度。計數器限流:這是一種比較簡單粗暴的算法,主要用來限制總并發數,比如數據庫連接池、線程池、秒殺的并發數;計數器限流只要一定時間內的總請求數超過設定的閥值則進行限流。
(5)黑名單限流
如果此用戶進行過很多非法操作,或者說專門有一個中黑系統,經過分析之后直接將此用戶列入黑名單,所有請求直接返回錯誤碼。我們可以給每個用戶設置一個狀態比如包括:初始化狀態,正常狀態,中黑狀態,關閉狀態等等;或者我們直接通過分布式配置中心,直接保存黑名單列表,每次檢查是否在列表中即可。
常用的安全防范有以下方式:API Keys、Basic Auth、Hmac、OAuth。本文主要討論基于OAuth協議的API安全驗證。本文主要介紹基于OAuth協議(Client Credentials)的實踐。
- 系統架構
下圖是一個筆者親身經歷的一個例子,到目前為止,運轉良好。API服務服務網關采用基于Kong API網關的實現,認證中心是實現OAuth 2.0協議的權限認證系統,服務提供系統設置基于IP的白名單訪問。
OAuth 2.0是一種用戶驗證和授權用戶的流行方法,此方法依賴于身份驗證服務器和API服務器進行通信以授予訪問權限,在筆者的前面的文章中已經詳細闡述OAuth的原理,在這里就不在贅述,想看的讀者可以翻看歷史記錄找到該篇文章。下圖標注了詳細的流程。
- JWT令牌的理論
JWT(全稱:Json Web Token)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。如有讀者想了解詳細的JWT規范,可以訪問RFC7519的官方網站:https://datatracker.ietf.org/doc/html/rfc7519。
JWT由3部分組成:標頭(Header)、有效載荷(Payload)和簽名(Signature)。在傳輸的時候,會將JWT的3部分分別進行Base64編碼后用。進行連接形成最終傳輸的字符串。JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)。
下圖為一個簡單的JWT的解碼出來的例子:
以上圖為例,JWT的數據結構一般是一個三段式的字符,以“.”隔開:xxxxx.yyyyy.zzzzz。
JWT第一部分是Header頭部分,它是一個描述JWT元數據的Json對象,通常如下所示。alg屬性表示簽名使用的算法,默認為HMAC SHA256(寫為HS256),typ屬性表示令牌的類型,JWT令牌統一寫為JWT。最后,使用Base64 URL算法將上述JSON對象轉換為字符串保存。
JWT第二部分是Payload,也是一個Json對象,除了包含需要傳遞的數據,還有七個默認的字段供選擇。分別是,iss:發行人、exp:到期時間、sub:主題、aud:用戶、nbf:在此之前不可用、iat:發布時間、jti:JWT ID用于標識該JWT。也可以加上自定義字段。需要注意的是,默認情況下JWT是未加密的,任何人都可以解讀其內容,因此如果一些敏感信息不要存放在此,以防信息泄露。JSON對象也使用Base64 URL算法轉換為字符串保存。
JWT第三部分是Signature簽名。是這樣生成的,首先需要指定一個secret,該secret僅僅保存在服務器中,保證不能讓其他用戶知道。然后使用Header指定的算法對Header和Payload進行計算,然后就得出一個簽名哈希。也就是Signature。
那么Application Server如何進行驗證呢?可以利用JWT前兩段,用同一套哈希算法和同一個secret計算一個簽名值,然后把計算出來的簽名值和收到的JWT第三段比較,如果相同則認證通過。
JWT的優點包括如下:JSON格式的通用性,所以JWT可以跨語言支持,比如JAVA、JavaScript、php、Node等等??梢岳肞ayload存儲一些非敏感的信息。便于傳輸,JWT結構簡單,字節占用小。不需要在服務端保存會話信息,易于應用的擴展。
- Kong API網關的JWT令牌應用
下圖是Kong認證的實際過程。
如果配置中允許OPTIONS請求,并且請求的方法確實是OPTIONS,放行。
如果配置中設置了匿名消費者,并且有憑證,放行。這里是為了實現多重認證,通過配置匿名消費者來實現邏輯或的認證,此時必須有其他認證插件,當其他認證插件通過,jwt插件就不再驗證,若不設置匿名,則既要jwt驗證也要其他認證插件驗證。
先后從query params,request header中查找token(根據配置的變量),token的格式為\s*[Bb]earer\s+(.+),如果從query params找到就返回了,不會再根據request header找。
如果沒有找到token,或者token格式不對,并且不允許匿名,返回401。
如果沒有找到token,并且允許匿名,根據配置的匿名的id查詢消費者,增加request header:X-Consumer-ID、X-Consumer-Username和X-Anonymous-Consumer=true,id是自動生成的,username是配置時填寫的,設置這個消費者的憑證是nil,放行。
如果找到token,且格式合法,jwt解碼這個token,驗證token的 claim,消費者,簽名驗證,過期驗證,全部通過后,同匿名一樣,增加步驟5中的request header,并且清除X-Anonymous-Consumer這個header。
以下是Kong網關集成OAuth插件的例子,首先啟用OAuth插件,然后在將插件添加到需要集成權限的Service上。
請求的時候只要在HTTP Header帶上“Content-Type:application/json”和“Authorization: Bearer 認證中心申請的JWT令牌”即可訪問到所需的資源。
- Spring Cloud Gateway的JTW令牌應用
Spring Cloud Gateway是Spring官方基于Spring 5.0、Spring Boot 2.0和Project Reactor等技術開發的網關,Spring Cloud Gateway旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。Spring Cloud Gateway作為Spring Cloud生態系中的網關,目標是替代ZUUL,其不僅提供統一的路由方式,并且基于Filter鏈的方式提供了網關基本的功能,例如:安全,監控/埋點,和限流等。
使用Gateway全局過濾器獲取JWT Token后進行用戶認證及鑒權。下圖為實現該過濾器的代碼。
- JWT令牌應用實例
1、登錄獲取token
2、將token設置到Request Header后,訪問資源服務
3、當沒有token訪問資源服務時就會返回401
- 總結
最后講講JWT的缺點,任何技術都不是完美的,所以我們得用辯證思維去看待任何一項技術。安全性沒法保證,所以jwt里不能存儲敏感數據。因為jwt的payload并沒有加密,只是用Base64編碼而已。無法中途廢棄。因為一旦簽發了一個jwt,在到期之前始終都是有效的,如果用戶信息發生更新了,只能等舊的jwt過期后重新簽發新的jwt。
續簽問題。當簽發的jwt保存在客戶端,客戶端一直在操作頁面,按道理應該一直為客戶端續長有效時間,否則當jwt有效期到了就會導致用戶需要重新登錄。那么怎么為jwt續簽呢?最簡單粗暴就是每次簽發新的jwt,但是由于過于暴力,會影響性能。如果要優雅一點,又要引入redis解決,但是這又把無狀態的jwt硬生生變成了有狀態的,違背了初衷。所以印證了那句話,沒有最好的技術,只有適合的技術。感謝大家的閱讀,希望看完之后能對你有所收獲。
本文簡單的討論API權限認證,關于API的限流等,后續將持續分享其他內容。碼字不易,歡迎關注、點贊、收藏。