Restful API 安全

原文鏈接:http://openwares.net/misc/restful_api_security.html

restful是一種輕量級的web service實現方式。restful並不是標準,也沒有對應的軟件實體,只是基於http協議的一種指導性的設計方法或原則。

當下流行的open api大部分採用restful的方式實現,但restful並不限於此。普通的web app,移動app,以及系統之間的web service集成都可以採用restful的方式。

restful和http一樣是無狀態的,也就是單次請求之間並無任何聯繫,每次請求必須攜帶全部的狀態信息。

對於受保護的資源,restful同樣面臨認證(authentication)和授權(authorization)的問題。

restful在不同的使用情景下有其相適宜的認證和授權方式。

第三方授權

現在火熱的開放平臺就是典型的三方授權模式。OAuth(Open Authorization)就是用於三方授權的協議,當前版本爲2.0。
OAuth協議重在授權,應用程序無需知道資源擁有者的身份憑證,只需用戶授權一定的資源訪問權限,獲取相應的Access Token就可以在授權範圍內訪問用戶的資源。這種授權是有期限的,而且用戶可以隨時撤銷。

OAuth授權整個流程涉及到用戶,資源服務器,認證服務器和第三方應用這四個角色。資源服務器和認證服務器只是概念上的區分,物理上可以存在於同一個服務器。

Access Token就是與一個與用戶相關的一個隨機數,認證服務器記錄了此Access Token所擁有的訪問權限。第三方應用訪問用戶資源時攜帶Access Token,經過權限檢查可以訪問被授權的資源。

web應用

前後分離的web應用程序,後端可以採用restful方式向前端提供api接口。這種模式,使用傳統的session方式即可滿足要求。也可以採用token的方式,用戶使用身份憑證通過系統身份認證後,服務端頒發一個隨機的token,以後每次訪問api時,參數中攜帶此token即可。token可以設置有效期,過期以後重新認證頒發新的token。

其實傳統的session使用的sessionid就是token。無論使用cookie,url重寫還是隱藏表單域,無非都是將服務器頒發的sessionid再重新發送給服務器進行認證。sessionid就是會話令牌。

session用於認證,授權則由應用程序自行處理,比如基於角色的權限系統等。

傳統的session方式最大的風險在於session劫持,https可以大大緩解這一風險,可以杜絕中間人攻擊。

web接口

restful方式實現web service供其他應用使用。這種情形下通過頒發app id(access key/pulic key)和app secret(secret key/private key),並對請求進行簽名的方式來保證api的安全,防止有人篡改請求和非授權訪問。如果簽名中添加timestamp可以進一步防範重放攻擊(replay attack)。

此處的access key用戶標示用戶的身份,客戶端必須妥善保存其secret key,這是服務端認證客戶端唯一可靠保證。
access key類似傳統的用戶名,而secret key則是兩端共享的私密祕鑰,以隨機數生成算法生成一個較長的隨機字符串即可。

客戶請求api時,將請求的動作類型(get,post,put或delete)、uri、請求參數(包括access key)、timestamp使用secret key進行簽名,使用HMAC-SHA256等摘要算法。將計算好的摘要同其他請求參與一同發送給服務器。服務端根據access key查找其對應的secret key,然後使用相同的算法重新計算摘要。如果重新計算的摘要與請求傳送過來的摘要一致,則可以信任此次請求。添加時間戳的主要目的是用於防範重放攻擊。

簽名算法

下面是一個簽名算法的例子。

算法描述如下:

signature = HMAC-SHA256(secrey_key, string_to_sign);

string_to_sign = http_verb + "&" + uri + "&" + request_parameters_sorted;

request_parameters_sorted = "key1=value1&key2=value2&..keyn=valuen";

比如以post方式訪問https://foo.com/bar/test接口,請求參數爲:

{
  "first_name" : "kitty",
  "last_name" : "san",
  "age" : 8,
  "gender" : "female",
  "app_id" : "xdfe323423fsvdsefew",
  "timestamp" : "2015-10-21 12:06:06"
}

獲取當前的UTC時間戳爲: 2015-10-21 12:06:06

將請求參數的key以字典序排序得到:

{
  "age" : 8, 
  "app_id" : "xdfe323423fsvdsefew",
  "first_name" : "kitty",
  "gender" : "female",
  "last_name" : "san",
  "timestamp" : "2015-10-21 12:06:06"
}

將排序後的參數拼接得到request_parameters_sorted:

request_parameters_sorted = "age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06";

然後得到將要簽署的字符串string_to_sign

string_to_sign = "post&/bar/test&age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06"

最後得到signature

signature = HMAC-SHA256('your_secret_key', "post&/bar/test&age=8&app_id=xdfe323423fsvdsefew&first_name=kitty&gender=female&last_name=san&timestamp=2015-10-21 12:06:06");

計算完簽名後,將簽名作爲請求參數之一一起發送給服務端,參數的名稱爲signature。服務器端接收到請求後以相同算法重新計算簽名進行覈對即可。服務器端計算簽名時要去掉signature參數。

所有的字符都使用UTF-8編碼。直接對原始請求參數進行簽名即可,無需進行url編碼。服務器端接收到的也是原始請求參數,這樣計算簽名更簡單。

防範重放攻擊

因爲請求中攜帶了請求發出時的時間戳,服務器可以設置一個合理的請求認證時間窗口,比如10分鐘,在當前時間前後10分鐘之內的請求都可以視爲合法請求。這只是減少了被重放攻擊的可能性,但並未完全杜絕重放攻擊。如果請求在服務器時間窗內被截獲重放,則只靠時間戳是無能爲力的。

因此,需要附加另外的機制來防止重放攻擊。服務端可以記錄每次請求的時間戳和簽名,每次請求到達是,先驗證請求是否在時間窗口範圍內,如果超出時間範圍則直接拒絕。如果在時間窗口內,則查詢請求記錄,如果沒有對應的請求記錄,則滿足此次請求,並將此次請求的時間戳和簽名記錄下來,並清理掉不在當前時間窗口內的所有請求。

時間戳加記錄請求的方式可以完全杜絕重放攻擊,而且可以保持一個很小的請求記錄表。因爲在當前時間窗口外的請求可以隨時被清理掉。

請求速率限制

如有需要可以對api請求的速率或次數進行限制。

非對稱祕鑰加密簽名

也可以使用RSA非對稱祕鑰進行數字簽名。

服務端生成RSA公私密鑰對和客戶端的access key,將私鑰和access key交付客戶端應用。服務端保存access key和客戶公鑰。

請求籤名時,客戶端不再使用HMAC簽名算法,而是使用普通的摘要算法,比如SHA256,但此時傳送的摘要使用客戶私鑰進行加密後再隨請求參數一起傳遞。因爲誰都可以計算SHA256摘要,所以需要用私鑰進行加密保護。

服務端接收到請求後,使用同樣的算法計算SHA256摘要,然後使用客戶的公鑰解密隨請求一起發的、客戶端計算的、加密後的摘要,如果服務端重新計算的摘要與解密後的摘要相同,則認爲請求是合法。

無論使用對稱祕鑰還是非對稱祕鑰進行簽名,祕鑰都要妥善保存,這是整個簽名認證算法的基石。

無論何種情形,對於restful api的安全而言,https/ssl加密都是十分有必要的。

注意:使用access key與secret key並對api簽名調用的方式,並不適合web前端或者移動app使用。因爲web前端無法保密secret key,而將secret key保存在android或ios app中也是無法保證安全的,很容易將secret key從app中破解出來。只有將secret key保存在後端才能保證安全。並且secret key是針對客戶端發放的,而不應該是針對每一個客戶端的user發放的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章