RESTful Api 身份認證中的安全性設計探討

Bugtags發表於2016-01-13

REST 是一種軟體架構風格。RESTful Api 是基於 HTTP 協議的 Api,是無狀態傳輸。它的核心是將所有的 Api 都理解為一個網路資源。將所有的客戶端和伺服器的狀態轉移(動作)封裝到 HTTP 請求的 Method 之中.

而本篇文章則主要是討論 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 請求的作用是僅僅防止二次請求,對身份認證並沒有什麼提升。

Access Token

不知道是否應該這麼稱呼。原理即當客戶端登入完畢之後,給客戶端返回一個 token,伺服器端控制該 token 的有效期,每次請求都帶上該值,然後伺服器端做驗證,退出之後,客戶端通知服務端端銷燬 token,客戶端本地也銷燬。但是如果抓包獲取到 token,就能任意偽造請求了。

同時 api 介面還存在被第三方開發者或者公司隨意利用的風險。也就是說,別人可以非常輕易的就弄出一個你們 app 的複製版,而且還用的你們的所有資源。

危險性高,實際開發估計使用得還不少。

Api Key + Security Key + Sign

下圖是我們自己每次請求的身份認證的方式,如有不足,請大家指出。可以說是 JWT 的自定義版吧。

enter image description here 這裡的認證邏輯即:

使用者登入返回一個 api_key 和 security_key;

然後客戶端將 security_key 存在客戶端;

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

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

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

然後根據 api_key 得到 sercurity_key;

最後驗證 sign。

Api key 的作用是什麼?(補)

看到有朋友在頭條問了這個問題,說下我的實際使用場景:

api key 是用來標識每個不同使用者的(也就是說 api key 和使用者 id 一一對應的),同時也用來驗證security_key 和 sign 的。

比如有 2000 萬使用者,以 redis 作為資料庫,將 api_key 為鍵,security_key 作為值,api_key 雜湊分佈(比如對末尾位字元的 ASCII 對 20 取模)到 20 個 hashes 裡。

當使用者請求過來的時候首先根據 api_key 找到對應的 hashes,首先 HEXISTS 檢查該 api_key 是否存在,存在則通過 HGET 取出該值,最後一起驗證 sign。

是否需要加上時間戳驗證?

上面的認證邏輯中加密得到簽名的時候,把時間戳加進去是為了在一定程度上遮蔽了一些無效的請求,可以略去,也可以設計的更加嚴格。如果想防止惡意的 api ddos 攻擊,這一步驗證肯定是不行的。需要做更多的驗證,比如使用者驗證,ip 驗證等。可以參考 github 的 api 的設計。它會在返回的 http 頭資訊裡帶上

X-RateLimit-Limit: 5000

X-RateLimit-Remaining: 4999

表示這個介面在某一時間段內,該授權使用者呼叫該介面的最大次數為 5000次,該時間段內還剩餘 4999 次。當然,這樣的驗證加上之後,在程式碼的執行效率上肯定會有所影響。

是否需要將 request_parameters 也加入到 sign 生成的演算法之中?

也不是必須的,僅僅是為了請求的真實性,減少請求的偽造,比如有人抓包拿到 http 請求之後,如果沒有驗證 sign 這步,那麼別人就可以非常簡單的修改請求的引數,而請求都會生效。

這裡將 request_parameters 也加入到簽名之中,就減少了偽造請求的可能性,但是無法杜絕,破壞者可能就非要黑你,又對逆向工程非常熟悉,找到我們加密演算法的實現,依然可以未知出合法的簽名,所以我們常說,伺服器端永遠不能相信客戶端的請求都是安全的、合法的,需要做驗證的都還是不能省略。

同時這(sign演算法)也造成了 api 介面除錯的成本,api 測試工具必須也得實現那一套演算法,或者是設定在開發環境下不做驗證。我們在配置開發環境的時候則是 vpn 連測試伺服器所在內網,然後進行測試,否則開發環境也存在被人利用的風險。

專案例項 https://github.com/zhoumengkang/netty-restful-server

JWT

JWT (JSON Web Token) 使用流程如下(圖片來自官網) enter image description here

其認證機制也是登入,發放金鑰給客戶端,然後客戶端每次傳送請求的時候通過 JWT 的演算法規則組裝 JWT 的Auth Header,伺服器端作驗證。

web 授權認證的原理萬變不離其宗,都是如此。

只不過 JWT 呢,自定了一套認證協議。格式為 Header.Payload.Signature。比如 xxxxx.yyyyy.zzzzz。簽名內容是有 Header+Payload+Secret 通過 HMAC SHA256 演算法加密而成。

enter image description here 而請求的很多引數鍵值對都可以放在 Payload 裡面。完整講解請求看官方的介紹 http://jwt.io/introduction/

需要注意的一點,依照 JWT 的協議,只有一個 secret,無法得知該使用者是誰,所以在 secret 該值中必須要可以解碼出使用者的 id。

而我們自定義認證協議的時候 header 感覺就沒有必要了,使用什麼演算法事先定義好即可。所以我們也沒選擇這種方式而是上面的那種方式。

其他

oauth2.0 則屬於第三方認證,不在本篇的討論範疇之內,可以閱讀 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

來自:周夢康

連結: http://mengkang.net/620.html

enter image description here

相關文章