沒有絕對的安全,這個話題很深, 下文都是自己的一些理解,水平有限,如有勘誤,希望大家予以指正。
由於 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 測試工具必須也得實現那一套算法,或者是設置在開發環境下不做驗證。我們在配置開發環境的時候則是 vpn 連測試服務器所在內網,然后進行測試,否則開發環境也存在被人利用的風險。
最近想拿一個小項目來試水RESTful Web API,項目只有幾個調用,比較簡單,但同樣需要身份驗證,如果是傳統的網站的話,那不用說,肯定是用戶名+密碼在登錄頁獲得登錄Token,並把登錄Token記在Cookie和Session中作為身份標識的這種方式,但現在不同了,關鍵是RESTful,這意味着我們設計出來的這些API是無狀態的(Stateless),下一次的調用請求和這一次的調用請求應該是完全無關的,也就是說,正宗的RESTful Web API應該是每次調用都應該包含了完整的信息,沒錯,包括身份信息!
如何實現RESTful Web API的身份驗證
最近想拿一個小項目來試水RESTful Web API,項目只有幾個調用,比較簡單,但同樣需要身份驗證,如果是傳統的網站的話,那不用說,肯定是用戶名+密碼在登錄頁獲得登錄Token,並把登錄Token記在Cookie和Session中作為身份標識的這種方式,但現在不同了,關鍵是RESTful,這意味着我們設計出來的這些API是無狀態的(Stateless),下一次的調用請求和這一次的調用請求應該是完全無關的,也就是說,正宗的RESTful Web API應該是每次調用都應該包含了完整的信息,沒錯,包括身份信息!
那如何確保安全?傳輸時給密碼做MD5加密?得了吧!這樣做只能讓你自己感覺“安全”點,其實沒什么任何用處,利用現在的技術(有種叫什么Rainbow Table啥的來着?本人外行,不是很懂)很快就能算出明文密碼了,而且如何防止挾持和重發攻擊?
也許你想到了,SSL,如果你打算采用SSL,請忘記一切自行設計的加密方案,因為SSL已經幫你做好了一切,包括防止監聽,防止挾持,防止重發……一切都幫你考慮好了,你大膽地把明文密碼寫在你的包中就OK了,我向你保證沒問題。
但SSL的缺點是服務器端配置相對有點復雜,更關鍵的就是客戶端對此支持可能不好,那你考慮一種自己的加密方法
