1. 程式人生 > >檔案下載許可權控制機制

檔案下載許可權控制機制

要對下載的許可權進行精確的控制(防止盜鏈,防止迅雷吸血,下載扣除積分等虛擬貨幣),以前接觸的方法有幾種:
1、通過rewrite不斷地更改下載檔案的url,並插入很多無意義的字元;
2、驗證下載連結的來路,或者cookie;
3、通過伺服器端程式(例如一個php檔案),open檔案,讀取內容然後返回給客戶端。

第一種方法很笨,而且吃力不討好;
第二種方法很容易破解,因為referer和cookie都是客戶端發出的,能夠方便地偽造,而且迅雷對此已經是輕車熟路;
第三種方法是可行的有效的,所有的檔案都經過一個程式讀取併發送,在讀取之前可以有效的驗證許可權,但是下載過程中始終要佔用一個cgi執行緒,而且一般cgi語言的IO效能都不好,速度很慢,佔用了伺服器的大量資源,導致總體效率極其低下,難以大規模運用。

為此我研究了一下csdn下載頻道的實現機制。
csdn下載頻道能夠有效的驗證許可權,扣除積分,而且不排斥迅雷等下載客戶端,同一個使用者下載同一個檔案也不會重複扣除積分,而且下載時始終沒有暴露檔案的真實地址,同一個下載URL到了別的地方也完全不可用,可以說是實現得比較理想的。

我選擇了一個檔案進行測試,下載的url是: http://dldx.csdn.net/fd.php?i=573624740728082&s=4fc2353ca769a0ebd9237b6f98791679

這個url向檔案儲存伺服器上的fd.php檔案傳送了兩個經過加密的引數,裡面應該包含有使用者登入資訊(使用者ID和sid)和目標檔案的ID號。
用迅雷下載這個檔案,截獲返回的頭資訊:
Host: dldx.csdn.net
Pragma: no-cache
Range: bytes=0-
Referer: http://d.download.csdn.net/down/2474072/waf9898
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; )
HTTP/1.1 206 Partial Content
Server: nginx/0.7.65
Date: Tue, 22 Jun 2010 07:08:21 GMT
Content-Type: “application/octet-stream; charset=utf-8″
Content-Length: 667747
Last-Modified: Mon, 21 Jun 2010 23:45:02 GMT
Connection: keep-alive
Content-Disposition: attachment; filename=”DNF%E6%82%A0%E6%82%A05%5B1%5D.7.rar”
Expires: 0
Cache-Control: must-revalidate, post-check=0, pre-check=0
Content-Range: bytes 0-667746/667747

這裡面始終沒有暴露目標檔案的真實路徑,不是一般下載系統所使用的header重定向的方式。而且有一個重新命名的訊號。伺服器使用的程式是nginx/0.7.65。
根據這些資訊,在google搜尋到這篇文章:http://kovyrin.net/2006/11/01/nginx-x-accel-redirect-php-rails/
顯然,csdn就是使用了文中所說的nginx X-Accel-Redirect。

解釋一下整個過程:

步驟1,客戶端請求http://dldx.csdn.net/fd.php ,並傳遞相關資訊;

步驟2,fd.php根據所傳遞的資訊判斷出訪問者的身份和所請求的資源,然後應該驗證了客戶端的IP,進一步判斷其許可權。如果這個客戶端有權下載 此檔案,則在HTTP header加入X-Accel-Redirect: (檔案的真實路徑),並加上head Content-Type和Content-Disposition:;

步驟3,nginx得到fd.php的迴應後發現帶有X-Accel-Redirect的header,那麼根據這個頭記錄的路徑資訊開啟目標檔案;

步驟4,nginx把開啟檔案的內容返回給客戶端。

這樣所有的許可權檢查和積分扣除等操作都可以在步驟2內完成,而且fd.php返回帶X-Accel-Redirect的頭後,其執行已經終止,剩下 的傳輸檔案的工作由nginx 來接管,同時X-Accel-Redirect頭的資訊被nginx刪除,不會返回給客戶端,也就不會暴露(實際上可以把目標檔案儲存在不能經由web訪 問的目錄),並且由於nginx在開啟靜態檔案上使用了 sendfile(),其IO效率非常高,比php的IO要快上N++倍。

這是一種優雅,有效,高效的實現方案。

因為沒有架設過nginx伺服器,我希望能在apache實現這個功能,於是查找了一下有沒有類似的mod,果然查詢到了一個 mod_xsendfile:http://tn123.ath.cx/mod_xsendfile/ ,其實現機制與nginx的X-Accel-Redirect基本相同。

下載之後在本機測試。

1、載入mod_xsendfile。將檔案 mod_xsendfile.so 移動到 apache/modules 目錄下,將以下內容新增到httpd.conf中
LoadModule xsendfile_module modules/mod_xsendfile.so
XSendFile On
XSendFileAllowAbove On

2、使用PHP呼叫X-sendfile。程式碼如下:
接收_GET資料並解密;
驗證uid、sid、檔案id;
如果通過驗證:

扣除積分、計數統計等操作;
header('Content-Type:(目標檔案型別)');
header('Content-Disposition: attachment; filename="(希望客戶下載到的檔名)"');
header('X-Sendfile:(目標檔案真實路徑,使用絕對路徑,例如"E:/www/dl/test.rar',此路徑可以是web無法訪問的目錄")');
exit;

如果不通過:

給客戶端返回一個提示性的html檔案;

?>

3、構造下載url,用迅雷成功下載;破壞驗證條件(比如改變客戶端IP)之後,迅雷只能下載到提示錯誤的檔案。

實際應用中可以採用以下具體方案:
1、把所有的目標檔案都儲存在伺服器B,此伺服器不需要資料庫,而且通過web只能訪問到某入口檔案(比如http://dldx.csdn.net/fd.php),在這個檔案中配合apache實現X-Sendfile;
2、 網站檔案(php和html),以及資料庫執行在伺服器A(當然資料庫也可以另設伺服器),此伺服器負責構造類似於 http://dldx.csdn.net/fd.php?i=573624740728082&s=4fc2353ca769a0ebd9237b6f98791679 的url;
3、伺服器B接到以上URL以後,分析客戶端IP,然後遠端連線伺服器A的資料庫,把uid,sid,檔案id,客戶端IP進行匹配分析,通過則扣除積分放行下載,否則提示錯誤。

此方案最終就能夠實現以下目的:
1、任何方式都無法直接通過web訪問到目標檔案,迅雷也沒有辦法;
2、類似於 http://dldx.csdn.net/fd.php?i=573624740728082&s=4fc2353ca769a0ebd9237b6f98791679 的URL沒有通用性,只能特定的使用者在特定的IP訪問特定的檔案,迅雷即使把這個URL儲存起來,也是沒有辦法吸血的(只能下載到提示錯誤的檔案);
3、檔案儲存和資料庫兩臺伺服器乾淨的分割,便於維護;
4、速度和效果都很完美,不會產生驗證差錯,也不會過多佔用伺服器資源。