RESTful Api 身份認證安全性設計
REST是一種軟件架構風格。RESTful Api 是基于 HTTP 協議的 Api,是無狀態傳輸。它的核心是將所有的 Api 都理解為一個網絡資源。將所有的客戶端和服務器的狀態轉移(動作)封裝到 HTTP 請求的 Method 之中。
詳情可以閱讀 http://mengkang.net/620.html 。
而這篇文章則主要是討論 RESTful Api 身份認證安全性設計。
沒有絕對的安全,這個話題很深, 下文都是自己的一些理解,水平有限,如有勘誤,希望大家予以指正。
由于 RESTful Api 是基于 Http 協議的 Api,是無狀態傳輸,所以 只要和用戶身份有關的 請求 都會帶上身份認證信息。(很多時候客戶端事先并不知道某個 api 后期會不會加入身份判斷,所以我們一般都會選擇每個請求都會帶上認證信息,如果有的話。)
Http Basic Authentication
Http Basic 是一種比較簡單的身份認證方式。 在 Http header 中添加鍵值對 Authorization: Basic xxx (xxx 是 username:passowrd base64 值)。
例如 username 為 zmk ,password 為 123456,請求則如下
GET /auth/basic/ HTTP/1.1 Host: xxxxx Authorization: Basic em1rOjEyMzQ1Ng==
而Base64 的解碼是非常方便的,如果不使用 Https ,相當于是帳號密碼直接暴露在請求中。
危險性高,實際開發者使用的應該幾乎為0。
順便提下 DIGEST 認證,和 BASIC 認證相差無幾,而且不適合 api 設計,實際又需要兩次請求,首次請求,服務器端返回401,并且帶上 nonce 值,然后客戶端再利用 username + password + nonce 默認MD5之后再請求。對 http 請求的作用是僅僅防止二次請求,對身份認證并沒有什么提升。
Cookie + Session
不知道是否應該這么稱呼,只是覺得類似于 cookie 與 session 的機制。
原理即當客戶端登錄完畢之后,給客戶端返回一個 cookie ,服務器端控制該 session 的有效期, 每次請求都帶上該值,然后服務器端做驗證,退出之后,客戶端通知服務端端銷毀 session ,自身銷毀 cookie 。但是如果抓包獲取到 cookie ,就能任意偽造請求了。
危險性高,實際開發估計使用得還不少。
Api Key + Security Key + Sign
下圖是我們自己每次請求的身份認證的方式,如有不足,請大家指出。可以說是 JWT 的自定義版吧。
這里的認證邏輯即:
-
用戶登錄返回一個 api_key 和 security_key ;
-
然后客戶端將 security_key 存在客戶端;
-
當要發送請求之前,通過 function2 加密方法,把如圖所示的五個值一起加密,得到一個 sign ;
-
發送請求的時候,則將除去 security_key 之外的值,以及 sign 一起發送給服務器端;
-
服務器端首先驗證時間戳是否有效,比如是服務器時間戳5分鐘之前的請求視為無效;
-
然后根據 api_key 驗證 sercurity_key ;
-
最后驗證 sign 。
是否需要加上時間戳驗證?
上面的認證邏輯中加密得到簽名的時候,把時間戳加進去是為了在一定程度上屏蔽了一些無效的請求,可以略去,也可以設計的更加嚴格。 如果想防止惡意的 api ddos 攻擊,這一步驗證肯定是不行的。需要做更多的驗證,比如用戶驗證,ip 驗證等。 可以參考 github 的 api 的設計 。它會在返回的 http 頭信息里帶上
X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 4999
表示這個接口在某一時間段內,該授權用戶調用該接口的最大次數為5000次,該時間段內還剩余4999次。當然,這樣的驗證加上之后,在代碼的執行效率上肯定會有所影響。
是否需要將 request_parameters 也加入到 sign 生成的算法之中?
也不是必須的,僅僅是為了請求的真實性,減少請求的偽造,比如 有人抓包拿到 http 請求之后,如果沒有驗證 sign 這步,那么別人就可以非常簡單的修改請求的參數,而請求都會生效。
血的教訓,自己經歷的一個實際案例:
一個取消用戶喜歡的標簽的接口,該接口會向服務器端發送類似于 ids=1,2,3,4 這樣的 request_parameters ,然后服務器端拿到這些 id 之后切割,然后將該用戶和這些標簽的關系從 user_tag 表中刪除。某個周末,數據庫服務器報警,而依照我們用戶習慣,那個時間不存在流量高峰,這個報警很不正常,正準備處理,報警結束了,但是過了一段時間就有用戶反應他們喜歡的標簽都被刪了。
通過查詢數據庫的慢日志,發現有很多注入的 sql。
DELETE FROM `user_tag` WHERE uid=4385328 AND tid=1 OR 14=14; DELETE FROM `user_tag` WHERE uid=4385328 AND tid=1 OR 91=91;
原來 沒有對切割之后的 id 沒有做數字驗證,估計黑客就是傳的 ids=1 OR 14=14,2,3 ,而一個 delete 操作可能超時,他丫的就搞了很多次請求,真是夠狠的。
幸運,數據庫還有定時的打包備份,大部分用戶的數據還是恢復了,同時修復了這一漏洞。
所以如果這里將 request_parameters 也加入到簽名之中,就減少了偽造請求的可能性,但是無法杜絕,破壞者可能就非要黑你,又對逆向工程非常熟悉,找到我們加密算法的實現,依然可以未知出合法的簽名,所以我們常說,服務器端永遠不能相信客戶端的請求都是安全的、合法的,需要做驗證的都還是不能省略。
同時這( sign 算法)也造成了 api 接口調試的成本,api 測試工具必須也得實現那一套算法,或者是設置在開發環境下不做驗證。我們在配置開發環境的時候則是 V*N 連測試服務器所在內網,然后進行測試,否則開發環境也存在被人利用的風險。
JWT
JWT ( JSON Web Token ) 使用流程如下(圖片來自官網)
其認證機制也是登錄,發放密鑰給客戶端, 然后客戶端每次發送請求的時候通過 JWT 的算法規則組裝 JWT 的 Auth Header ,服務器端作驗證。
web 授權認證的原理萬變不離其宗,都是如此。
只不過 JWT 呢,自定了一套認證協議。格式為 Header . Payload . Signature 。比如 xxxxx.yyyyy.zzzzz 。簽名內容是有 Header + Payload + Secret 通過 HMAC SHA256 算法加密而成。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
而請求的很多參數鍵值對都可以放在 Payload 里面。完整講解請求看官方的介紹 http://jwt.io/introduction/
需要注意的一點 ,依照 JWT 的協議,只有一個 secret ,無法得知該用戶是誰,所以在 secret 該值中必須要可以解碼出用戶的id。
而我們自定義認證協議的時候 header 感覺就沒有必要了,使用什么算法事先定義好即可。所以我們也沒選擇這種方式而是上面的那種方式。
一個基于 netty 的嚴格的安全驗證的輕量級的 RESTful Api Server https://github.com/zhoumengkang/netty-restful-server