RESTful Api 身份認證安全性設計

jopen 8年前發布 | 125K 次閱讀 API REST WEB服務/RPC/SOA

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 的自定義版吧。

這里的認證邏輯即:

  1. 用戶登錄返回一個 api_key 和 security_key ;

  2. 然后客戶端將 security_key 存在客戶端;

  3. 當要發送請求之前,通過 function2 加密方法,把如圖所示的五個值一起加密,得到一個 sign ;

  4. 發送請求的時候,則將除去 security_key 之外的值,以及 sign 一起發送給服務器端;

  5. 服務器端首先驗證時間戳是否有效,比如是服務器時間戳5分鐘之前的請求視為無效;

  6. 然后根據 api_key 驗證 sercurity_key ;

  7. 最后驗證 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

來自: http://mengkang.net/625.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!