標籤:
來源:http://meiyitianabc.blog.163.com/blog/static/10502212720131056273619/
我認為,保護伺服器端的資料,有這麼幾個關鍵點:
- 不能對使用體驗產生影響,這就排除掉了諸如每次介面調用都要求使用者輸入驗證碼這樣的做法
- 介面調用的網路互動需要無規律可循,比如article/1 –> article/1000 這樣的介面就太容易被其他人爬走了
- 要嚴格意義上阻擊爬蟲,需要每一次網路請求都是不可重放的,這樣才能避免其他人通過監聽網路互動並重放來爬取資料
- 對伺服器端編碼不產生太大影響,如果要對伺服器端傷筋動骨的大改,肯定是要不得的
通常,我們會採用一種簡單有效方法:對伺服器返回的資料加密來解決,但是,這種做法並沒有解決上面所提到的第二點,介面調用的時候url的規律性太強,網路監聽一下資料,就很容易找到url地址的規律了,加密的破解也很簡單,反編譯直接定位到解密函數,拿到密鑰。當然,在強大的反編譯工程面前,一切努力都是徒勞的,不管你用何種方法,都是可以把中間的邏輯找到並類比成一個用戶端來爬資料的。
我下面就提出一個破解更加複雜一些的方法,在用戶端產生請求時,對介面url進行RSA加密處理。
假設我們本來需要訪問 http://api.example.com/articles 這樣的一個介面,介面返回json資料。在用戶端訪問之前,我們先對這個url進行這樣的處理:
- 加用戶端時間戳記:http://api.example.com/1322470148/articles
- 對url的path段進行rsa加密,然後base64:http://api.example.com/TBhIskCgCN+WMK3PftbYzPQFAKvx9sE9OMOxvL00kCBlNiKw2C1Mb7oGcfUepTxauG06NLBNhr5BFtjt7Xu7uwdpUYyVcFRdI37SVyGRCOzaxACOGXGpX5dHZqQJia0icxwWJ+D1RiJqxFWQ++3/IgUOgDzgvQnPIl420bpztB8=
我們真實訪問的地址就變成了這樣一個長長的 url 結構,我們通過rsa演算法的padding參數和時間戳記,就可以讓這個後面長長的bas64串在每次訪問的時候都發生變化,同時,我們可以在伺服器端把一個小時之內的請求過的串都記下來,並不讓再次訪問,這樣就防止了爬蟲的重放請求嘗試。
在伺服器端,我們就需要在做響應之前,把url還原回來。在伺服器端,現在都是架構的天下,一般都有唯一的入口,如果使用的是php語言,主要在入口的index.php加上一些代碼就可以了:
| 123456789101112131415161718192021222324252627282930 |
if ($_SERVER[‘HTTP_HOST‘] == "api.example.com"){ // 只針對api這個網域名稱做處理 include_once dirname(__FILE__).‘/protected/components/EncryptUtil.php‘; // 加解密庫,你需要實現你自己的加解密類 $request_uri = $_SERVER[‘REQUEST_URI‘]; if(isset($_SERVER[‘HTTP_HOST‘])){ if(strpos($request_uri,$_SERVER[‘HTTP_HOST‘])!==false){ // 把 REQUEST_URI 中可能包含的host資訊去除掉 $request_uri=preg_replace(‘/^\w+:\/\/[^\/]+/‘,‘‘,$request_uri); } } $encoded = base64_decode(substr($request_uri, 1)); if($encoded && strlen($encoded) % 128 ===0){ $real_uri = EncryptUtil::private_decrypt($encoded); // 解密url路徑 if(!$real_uri){ echo ":)"; return; } // 解密失敗 if(preg_match("/([0-9]+)\\/(.+)/", $real_uri, $matches)){ // 提取出時間戳記和真實的url請求地址 $timestamp = $matches[1]; // 用戶端請求的時間戳記 $real_uri = $matches[2]; // 用戶端請求的真真實位址 $_SERVER[‘REQUEST_URI‘] = $real_uri; // 置上本來應該有的全域$_SERVER[‘REQUEST_URI‘] if(preg_match("/^[^?]+\\?(.+)/", $real_uri, $matches)){ $_SERVER[‘QUERY_STRING‘] = $matches[1]; // 置上本來應該有的全域$_SERVER[‘QUERY_STRING‘] parse_str($_SERVER[‘QUERY_STRING‘], $array); $_REQUEST = array_merge($_REQUEST, $array); // 置上本來應該被設定的全域$_REQUEST $_GET = array_merge($_GET, $array); // 置上本來應該被設定的全域$_GET } }else{ // url的格式不符合,沒有包含時間戳記 echo ":)"; return; } }else{ // url的長度不符合規則 echo ":)"; return; }} |
在經過這樣一段代碼處理之後,架構就一切正常,其他代碼都不需要做變更,就有了rsa加密的url支援,當然,這幾行代碼還是不能阻止重放攻擊的,裡面並沒有對請求過的url進行記錄處理,要實現url訪問的唯一性,還需要額外的更多代碼。
伺服器端完成了,那用戶端也同樣需要做相應操作,我這裡就不詳細講解了,貼上一段修改過的實際啟動並執行代碼,IOS,應用了 three20庫,併兼容TTURLRequest緩衝機制。
Android的Java版本我就把實際運行中的代碼的http部分抽離出來,因為牽涉到一些相關配置,代碼不能正常編譯,不過也放在這裡,以供參考。
android-rsa-http.zip
用法樣本:
| 123 |
BaiyiApiRequest request = new BaiyiApiRequest("articles/1");request.setListener(this);request.start(); |
http介面加密《一》:行動裝置 App中,通過在用戶端對訪問的url進行加密處理來保護伺服器上的資料