IHttpHandler的妙用之防盗链
昨天粗略讲了一下IHttpHandler接口的作用和动态给图片添加水印的处理,如果对这些不太清除的朋友,建议看看这篇《IHttpHandler的妙用(1):给图片添加水印》:http://blog.csdn.net/zhoufoxcn/archive/2008/01/10/2033530.aspx
昨天也提到了IHttpHandler接口主要有一个IsReusable属性和一个ProcessRequest方法,利用这个方法我们可以处理很多事情的,昨天我们利用了这个方法给图片动态添加了水印,今天我再来展示另一种用法。
大家查看一个msdn,可以看到它的声明如下:
Visual Basic(声明) Sub ProcessRequest ( _ context As HttpContext _ ) Visual Basic(用法) Dim instance As IHttpHandler Dim context As HttpContext instance.ProcessRequest(context) C# void ProcessRequest ( HttpContext context )
注意这个HttpContext对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session 和 Server)的引用。
有了它我们就方便多了,因为我们的下载资源一般都会有一个下载介绍(假设为
details.aspx?id=***),用户查看介绍之后,如果愿意下载,就会点击下载链接,这个链接也是一个页面(假设为
download.aspx?id=***),我们就可以得出结论,只要是用户通过我们的网站下载这些资源,那么在下载资源之前访问那个页面(简称前导
页,下同)一定是details.aspx,因此我们就可以得出结论只要是下载之前的前导页不是details.aspx这个页面,那个这个下载请求一定
是别的网站盗链(其实还可以放宽一点,在下载之前的前导页一定是本站的页面,也还可以要求更紧一点,下载之前访问的页面的id值一定要与下载的id值一
致,这就看大家的实际要求了)!
有了这个推论之后,我们就可以动手写代码了:
using System; using System.IO; using System.Web; /// <summary> /// 说明:DownloadHandler是一个防盗链的类,它可以防止本站资源被别的网站盗用 /// 作者:周公 /// 日期:2008-1-11 /// 首发地址:http://blog.csdn.net/zhoufoxcn /// </summary> public class DownloadHandler:IHttpHandler { public DownloadHandler() { // // TODO: 在此处添加构造函数逻辑 // } #region IHttpHandler 成员 /// <summary> /// 指示IHttpHandler 实例是否可再次使用 /// </summary> public bool IsReusable { get { return true; } } /// <summary> /// 处理请求的方法 /// </summary> /// <param name="context">它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session 和 Server)的引用。</param> public void ProcessRequest(HttpContext context) { Uri referrerUri = context.Request.UrlReferrer;//获取下载之前访问的那个页面的uri Uri currentUri = context.Request.Url; if (referrerUri == null)//没有前导页,直接访问下载页 { //输出提示,可以根据自身要求完善此处代码 context.Response.Write("请不要盗链本站资源,请从首页访问。<a href='index.aspx'>首页</a>"); return; } #region 判断前导页是否位于本站可以用此段代码 //if (referrerUri.Host == currentUri.Host)//前导页和当前请求页位于同一个主机 //{ // //用户是通过正常路径访问的,向用户提供下载文件 // //实际情况是根据id从数据库找到文件的物理路径,然后输出 // //为了简单代码,仅仅演示流程,这里我直接输出了文件 // //周公注。2008-1-11 // //获取请求的物理文件路径 // WriteFile(context); //} //else //{ // //输出提示,可以根据自身要求完善此处代码 // context.Response.Write("请不要盗链本站资源,请从首页访问。<a href='index.aspx'>首页</a>"); //} #endregion #region 判断前导页是否是我们的介绍页面 string referrerPage = referrerUri.LocalPath.Substring(referrerUri.LocalPath.LastIndexOf('/')+1); if (referrerPage == "Details.aspx")//如果前导页是我们的介绍页面 { //用户是通过正常路径访问的,向用户提供下载文件 //实际情况是根据id从数据库找到文件的物理路径,然后输出 //为了简单代码,仅仅演示流程,这里我直接输出了文件 //周公注。2008-1-11 //获取请求的物理文件路径 WriteFile(context); } else { //输出提示,可以根据自身要求完善此处代码 context.Response.Write("请不要盗链本站资源,请从首页访问。<a href='index.aspx'>首页</a>"); } #endregion } private void WriteFile(HttpContext context) { //用户是通过正常路径访问的,向用户提供下载文件 //实际情况是根据id从数据库找到文件的物理路径,然后输出 //为了简单代码,仅仅演示流程,这里我直接输出了文件 //周公注。2008-1-11 //获取请求的物理文件路径 string path = context.Request.PhysicalPath; //注意这里rar文件的ContentType是application/octet-stream //不同格式文件的contentType有可能不同 context.Response.ContentType = "application/octet-stream"; context.Response.WriteFile(path); } #endregion }
对web.config的配置:
<?xml version="1.0"?> <!-- 注意: 除了手动编辑此文件以外,您还可以使用 Web 管理工具来配置应用程序的设置。可以使用 Visual Studio 中的 “网站”->“Asp.Net 配置”选项。 设置和注释的完整列表在 machine.config.comments 中,该文件通常位于 WindowsMicrosoft.NetFrameworkv2.xConfig 中 --> <configuration> <appSettings> <!--添加到图片上的水印文字--> <add key="WaterMark" value="http://blog.csdn.net/zhoufoxcn"/> <!--水印文字的字体大小--> <add key="Font-Size" value="72"/> </appSettings> <connectionStrings/> <system.web> <!-- 设置 compilation debug="true" 将调试符号插入 已编译的页面中。但由于这会 影响性能,因此只在开发过程中将此值 设置为 true。 --> <compilation debug="true"/> <!-- 通过 <authentication> 节可以配置 ASP.NET 使用的 安全身份验证模式, 以标识传入的用户。 --> <authentication mode="Windows"/> <!-- 如果在执行请求的过程中出现未处理的错误, 则通过 <customErrors> 节可以配置相应的处理步骤。具体说来, 开发人员通过该节可以配置 要显示的 html 错误页 以代替错误堆栈跟踪。 <customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm"> <error statusCode="403" redirect="NoAccess.htm" /> <error statusCode="404" redirect="FileNotFound.htm" /> </customErrors> --> <httpHandlers> <!--只处理UploadImages目录下的jpg文件,别的目录下的图片不处理--> <add path="UploadImages/*.jpg" verb="*" type="ImageHandler"/> <!--所有的对zip、rar、iso文件的请求都由DownloadHandler处理--> <add path="*.zip" verb="*" type="DownloadHandler"/> <add path="*.rar" verb="*" type="DownloadHandler"/> <add path="*.iso" verb="*" type="DownloadHandler"/> </httpHandlers> </system.web> </configuration>
前导页(Details.aspx):
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Details.aspx.cs" Inherits="Details" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>无标题页</title> </head> <body> <form id="form1" runat="server"> <div> <a href="Download.aspx?id=1">下载</a> </div> </form> </body> </html>
下载页没有什么html代码,主要是后台代码。下载页后台代码(Download.aspx.cs):
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class Download : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { CheckUser(); //实际情况是根据id从数据库找到文件的物理路径,然后输出 //为了简单代码,仅仅演示流程,这里我直接输出了文件 //周公注。2008-1-11 //首发地址:http://blog.csdn.net/zhoufoxcn Response.Redirect("download/demo.rar");//这里就会交给DownloadHanddler处理了 } } //检查用户登录等信息 private void CheckUser() { //仅仅演示,具体代码根据具体要求编写 //比如用户没有登录如何处理,用户没有下载权限如何处理等 return; } }
正常流程截图:
(一)先打开介绍页面:
(二)点击下载链接进入下载页面:
此时出现文件保存对话框,情况正常。
(一)先打开介绍页面:
(二)点击下载链接进入下载页面:
此时出现文件保存对话框,情况正常。
再看盗链情况(非正常情况):
(一)打开首页(非前导页,跳过这一步也没有关系)
(二)直接访问下载页面或者直接访问下载文件的实际url地址:
此时出现了我们自定义的错误提示,甚至地址栏上都出现了该文件的物理地址,可是就是没有办法下载,此时就算是利用迅雷等下载软件,下载到的也是一个无效的文件,如图:
这样就达到我们的目地了,不是通过正常途径来下载是没有办法下载到他需要的文件的。
(一)打开首页(非前导页,跳过这一步也没有关系)
(二)直接访问下载页面或者直接访问下载文件的实际url地址:
此时出现了我们自定义的错误提示,甚至地址栏上都出现了该文件的物理地址,可是就是没有办法下载,此时就算是利用迅雷等下载软件,下载到的也是一个无效的文件,如图:
这样就达到我们的目地了,不是通过正常途径来下载是没有办法下载到他需要的文件的。
顺便说一下,写作本文时我是边编写代码边测试的(本博客原创的代码都是如此,测试通过之后方才发表,所以一般不会有什么问题,如果你学习的过程中发现有任何错误,请仔细检查你的代码,因为各人本地环境不同,出错原因各异,恕本人不一一指出你代码的错误之处)。