Loading... <div class="tip share">请注意,本文编写于 1776 天前,最后修改于 1776 天前,其中某些信息可能已经过时。</div> 要对下载的权限进行精确的控制(防止盗链,防止迅雷吸血,下载扣除积分等虚拟货币),以前接触的方法有几种: 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号。 用迅雷下载这个文件,截获返回的头信息: >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. 速度和效果都很完美,不会产生验证差错,也不会过多占用服务器资源。 最后修改:2020 年 02 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏