一次性搞懂服務端API安全解決方案
今年五月份去融貝網、獵豹移動面試的時候,被問到API安全加密的問題,很慚愧,自己回答的很不全面。自己也知道那是沒有徹底弄明白原理。然後,8月份的時候,上家公司新專案啟動時也和同事探討過,然後就去徹徹底底瞭解了一下,趁著熱乎勁兒還沒過去總結出來吧。
安全是相對的,下面是根據安全級別分析。我用簡單的PHP程式碼演示出來,只是演示,程式碼寫的不嚴謹請輕噴啊!!!
1、完全開放的介面
這種我們稱之為“裸奔API”,只要連上網就能呼叫,不存在安全,一般只能查詢,不能執行增、刪、改的操作。
<?php // 介面對外開放 public function noSecure($string) { $data = DB('table')->where('string', $string)->get(); if(!is_null($data)) { return $data; } } 複製程式碼
2、介面引數加密(基礎加密)
這種加密方式,只想讓特定的呼叫方使用。好比你把這些呼叫的人叫到一個小屋子,給他們宣佈說我這裡有個介面只打算給你們用,我給你們每人一把鑰匙,你們用的時候拿著這把鑰匙即可。
這把鑰匙就是我上文說到的引數加密規則,有了這個規則就能呼叫。
這有安全問題啊,這裡面的某個成員如果哪個不小心丟了鑰匙或者被人竊取,掌握鑰匙的人是不是也可以來掉用介面了呢?而且他可以複製很多鑰匙給不明不白的人用。
相當於有人拿到了你的請求連結,如果業務沒有對連結唯一性做判斷(實際上業務邏輯通常不會把每次請求的加密簽名記錄下來,所以不會做唯一性判斷),就會被重複呼叫,有一定安全漏洞,怎麼破?先看這個場景的程式碼,然後繼續往下看!
<?php // 介面加密 public function secureBySign($string, $appKey, $sign) { //檢驗簽名是否合法 $string = $_POST("string"); $appKey = $_POST("appKey"); $sign = $_POST("sign"); $signHelper = new SignHelper(); $currentSign = $signHelper->getSign($appKey, $string, ……); if($sign !== $currentSign) { return "簽名不合法"; } $data = DB('table')->where('string', $string)->get(); if(!is_null($data)) { return $data; } } 複製程式碼
3、介面引數加密+介面時效性驗證(一般達到這個級別已經非常安全了)
繼上一步,你發現有不明不白的人呼叫你的介面,你很不爽,隨即把真正需要呼叫介面的人又叫來,告訴他們每天給他們換一把鑰匙。和往常一樣,有個別夥伴的鑰匙被小偷偷走了,小偷煞費苦心,經過數天的踩點觀察,準備在一個月黑風高的夜晚動手。拿出鑰匙,搗鼓了半天也無法開啟你的神聖之門,因為小偷不知道你天天都在換新鑰匙。
小偷不服,經過一段時間琢磨,小偷發現了你們換鑰匙的規律。在一次獲得鑰匙之後,不加思索,當天就動手了,因為他知道他手裡的鑰匙在第二天你更換鑰匙後就失效了。
結果,小偷如願。怎麼破?先看這個場景的程式碼,然後繼續往下看!
<?php // 介面加密並根據時間戳判斷有效性 public function secureBySignExpired($string, $appKey, $sign, $timestamp) { //判斷請求是否過期---假設過期時間是20秒 $requestTime = GetDateTimeByTicks($timestamp); if(($requestTime + 20) < $_SERVER["REQUEST_TIME"]) { return "介面過期"; } //檢驗簽名是否合法 $string = $_POST("string"); $appKey = $_POST("appKey"); $sign = $_POST("sign"); $signHelper = new SignHelper(); $currentSign = $signHelper->getSign($appKey, $string, ……); if($sign !== $currentSign) { return "簽名不合法"; } $data = DB('table')->where('string', $string)->get(); if(!is_null($data)) { return $data; } } 複製程式碼
4、介面引數加密+時效性驗證+私鑰(達到這個級別安全性固若金湯)
繼上一步,你發現道高一尺魔高一丈,仍然有偷盜事情發生。咋辦呢?你打算下血本,給每個人配一把鑰匙的基礎上,再給每個人發個暗號,即使鑰匙被小偷弄去了,小偷沒有暗號,任然無法如願。即使小偷真正的如願,這樣也很容易定位是誰的暗號洩漏問題,找到問題根源,只需要給當前這個人換下鑰匙就行了,不用大動干戈。
但這個並不是萬無一失的,因為鑰匙和暗號畢竟還有可能被小偷搞到。程式碼如下:
<?php // 介面加密並根據時間戳判斷有效性而且帶著私有key校驗 // 在呼叫介面時動態從庫裡取,每個呼叫方在呼叫時帶上他的appSecret,呼叫方一般把自己的appSecret放到網站或APP配置檔案中 public function secureBySignExpiredKeySecret($string, $appKey, $sign, $timestamp) { //判斷請求是否過期---假設過期時間是20秒 $requestTime = GetDateTimeByTicks($timestamp); if(($requestTime + 20) < $_SERVER["REQUEST_TIME"]) { return "介面過期"; } // 根據appkey查庫獲取appSecret值 $appSecret = DB('table')->where('appKey', $appKey)->get('appSecret'); //檢驗簽名是否合法 $string = $_POST("string"); $appKey = $_POST("appKey"); $sign = $_POST("sign"); // 把appSecret加入進行加密 $signHelper = new SignHelper(); $currentSign = $signHelper->getSign($appKey, $appSecret, $string, ……); if($sign !== $currentSign) { return "簽名不合法"; } $data = DB('table')->where('string', $string)->get(); if(!is_null($data)) { return $data; } } 複製程式碼