ASP.NET中实现文件的保护性下载基础篇

时间:2022-06-01 16:15:14

一、文件保护性下载的需求

  如果我们需要在站点上出售数字形式的商品,如电子书、数字油画等,那么如何在供授权用户正常下载的同时又阻止非授权用户非法下载您的产品呢? 通过Forms身份验证,只能使这个问题得到部分解决。本文中,我将讲解如何防止某些用户访问站点上的某些文件;即使这些文件能够被这些用户直接浏览。

  解决这个问题的方法有多种,但是有些方法本身就有问题。本文中,我们将考察软件供应厂商常用的一些技术,然后再介绍一种新的解决方案。需要注意的是,这里介绍的是针对ASP.net站点的。

  二、常见的文件保护技术

  我们中很多人都有网上购买软件的经验,所以可能领教过用于文件下载的常见保护措施。下面,我们对它们进行考察。

  压缩文件口令保护

  这种保护方法比较简单,它不是防止您下载文件,而是防止未经授权的人员从压缩文件中提取文件的内容,因为WinZip和许多其他压缩程序都提供了口令保护功能。然而,这种方法的缺点也很明显,如果您允许某人访问该文件内容,那就必须给他提供口令,之后,您却无法阻止这个人将口令传给其他人。实际上,如果您搜索互联网的话,会发现各种各样的口令遍地都是。采用这种保护措施的时候,只能指望授权用户是有道德的人,不会将口令外传。或者,将这种保护措施提供一个层次,为每个人生成一个不同口令的压缩文件,然后传给他。 当然,这需要一个文件存储解决方案,因为需要能控制发送给用户的文件。这导致第二种文件保护方法。

  电子邮件

  许多软件供应厂商并不会把文件张贴到它们的网站上,而是向购买该软件的用户发送一封电子邮件,告知下载详细信息,或者直接连同文件一块发过去。 电子邮件可以包含文件下载链接,并限定该链接的有效时间。有时候,软件供应商还可以将这种这种技术跟口令保护相结合。文件一旦交到用户手里,剩下的保护措施就靠软件许可和注册了。其他基于电子邮件的解决方案还有动态生成文件名方法。

  临时文件名

  一些软件供应厂商会使用GUID或者其它的秘密的命名技术生成一个难以猜测的文件名,同时还可以令文件只能在规定时间内下载。

  三、技术分析

  虽然这些技术仍在使用,但是它们却不能在您的站点上开辟一片客户区域,使得用户能够检查他们的购买历史记录,并随时重新下载他们的软件。依我看来,提供了类似功能的站点能够提供更好的用户体验,对软件供应商来说也更容易管理——用户购买产品之后,您仅需给用户发送一封包含许可证密钥和他们在站点上相应客户区域的链接即可。这样的话,用户知道可以随时登录和下载软件,他们就会安心多了,即使弄丢了软件文件也不用怕了。

  为此,我们将介绍一种结合了ASP.net的Forms身份验证和称为HTTP处理程序的保护方案来提供这种良好的用户体验。类System.Web.UI.Page本身就是一个HTTP处理程序,并且会注册到您的机器的Web.config文件中。

内容导航

  四、HTTP处理程序

  实际上,使用ASP.NET定制HTTP处理程序并没有人们想象的那么复杂,下面我们用尽量容易理解的方式来讨论这一主题。HTTP处理程序的应用有很多,不过我们这里主要讨论它在文件保护问题方面的应用。

ASP.NET中实现文件的保护性下载基础篇

  图1 IIS中的扩展名映射

  下面我们将介绍什么是处理程序,及其工作原理,同时我们力争做到进行可能简单。在当ASP.NET环境中,您请求一个ASPX页面的时候,IIS会将该请求传递给相应的DLL来进行处理。所谓HTTP处理程序,就是处理IIS传给它们的请求的那些类。当您在机器上安装ASP.NET的时候,会随同向IIS添加一串表项(参见图 1)。这些表项包含希望ASP.NET处理的文件的扩展名(ASPX、ASMX等等)。当我们请求一个ASPX的时候,IIS收到该请求后,会将其传递给相应的DLL,在本例中为aspnet_isapi.dll,随后生产相应的HTTP处理程序实例来处理该请求。就ASPX页面而论,用到的HTTP处理程序是位于System.Web.UI命名空间中的一个Page类。

  就ASPX页面而论,页面处理程序用来控制和触发生命周期事件;当您浏览一个ASPX页面的时候,基本上一切工作都是由它来处理的。然而,您可以编写一个定制的HTTP处理程序来拦截浏览器发出的所有的请求,从而调整或者定制正常发生的动作。为此,我们需要用到多种技术,本文中我将首先讨论其中的IIS表项,以及Forms身份验证有关内容。

  五、IIS与Forms身份验证

  前面提过,IIS会把注册的扩展名发送到aspnet_isapi.dll。图1展示的是找到的已注册扩展名。我们可以在虚拟目录或者网站的“Properties”中的“Configuration”选项就可以看到这个对话框。带有由aspnet_isapi.dll进行处理的已注册扩展名的任何文件都受到ASP.NET的Forms身份验证的支配。下面我们对Forms身份验证的运行机制做简单介绍。

  定制的HTTP处理程序实际上就是实现了IHttpHandler接口的那些类。Forms身份验证允许您防止匿名用户在未授权的情况下访问某些web页面。文件web.config利用以下代码来设置Forms身份验证:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

   <authentication mode="Forms">
   <forms loginUrl="Login.aspx"/>
   </authentication>
   <authorization>
   <deny users="?"/>
   </authorization>

  上面的代码会防止没有通过身份验证的用户访问所有的页面。如果一个匿名用户试图访问一个网页,该代码会自动地将他们重定向到Login.aspx页面。这样一来,站点开发人员就能决定在这个页面中使用哪种身份验证方法,但是在ASP.NET 2.0中,开发人员可以很轻松地使用新的安全控件来完成此项工作。

  现在,我们说过这个代码能够阻止未通过身份验证的用户访问任何页面,但是准确来说它是阻止了未经认证的用户访问所有被aspnet_isapi.dll拦截的那些文件。这将在后面详加解释。为了给本文的后面的内容做铺垫,我们需要先描述示例电子商务站点的一些具体细节。

内容导航

  六、保护措施规划

  假设我们的站点使用户能在线购买软件,但是在购买或者下载软件之前,用户必须首先注册。然后,分别为用户和产品建立一个表,然后分别存放用户名和产品序列号。当用户购买软件的时候,通过在另一个表中创建一个记录来关联用户和产品。我们称这个表为UserProducts。

  我们想把所有的软件产品文件存储在一个称为files的文件夹中,该文件夹位于网站的根文件夹之中。产品表有一个字段,用于产品文件名,该文件名对应于files文件夹中的一个压缩文件。 我们称这个字段为ProductFileName。下面我们逐步介绍如何保护这些ZIP 文件。

  七、保护所有的压缩文件

  首先,我们要防止所有的压缩文件被未经验证的用户所下载。我们要让所有扩展名为.zip的文件经由ASP.NET 的Forms身份验证处理,这样匿名用户就不能访问它们了。虽然这一步并不是最关键的,但是它确实提供了文件的安全性。

  通常如果您能直接浏览某网站上的一个ZIP 文件,该站点会提示您打开或者将该文件保存到硬盘上。我们想让ASP.NET拦截对扩展名为zip的文件的请求,所以需要在IIS的应用程序映射表中添加相应的扩展名。

  为此,可以打开IIS的管理控制台,找到相应的站点或虚拟目录,单击右键并选择“Properties”选项,就会显示图 2。如果单击“Web Site”或者“Virtual Directory”选项卡中的“Configuration”按钮的话,就会看到扩展名表和用于处理它们的DLL,见图1。我们必须将扩展名“zip”添加到这个列表中,为此,可以单击“add”按钮,然后再扩展名文本框中键入“zip”,并单击“Limit to verb”选项按钮。

ASP.NET中实现文件的保护性下载基础篇

图2 网站属性

ASP.NET中实现文件的保护性下载基础篇

图3 导航至aspnet_isapi.dll

内容导航

  在Verbs文本框中,键入GET、HEAD、POST、DEBUG,以指示aspnet_isapi.dll拦截zip类型文件的请求。在Executable:文本框中,导航至aspnet_isapi.dll文件的位置,见图3。这个文件位于相应框架版本文件夹下的C:\WINDOWS\Microsoft.NET\Framework\目录中,如图4所示。

  ASP.NET中实现文件的保护性下载基础篇

图4 给IIS添加zip扩展名映射

  ASP.NET中实现文件的保护性下载基础篇

图5 映射zip扩展名

  建立这个表项后,我们的映射表将如图5所示。注意在这个表中的所有其他扩展名例如VBPROJ、CONFIG等等,aspnet_isapi.dll也会拦截这些扩展名以便进行保护。这就是当企图浏览web.config文件时会被重定向到一个拒绝页面的原因。

  在IIS中创建这个表项之后,如果尝试直接浏览我们站点上的ZIP文件的时候,如果用户没有通过身份验证的话,IIS会将其重定向到登录页面。所以,现在我们已经能够防止匿名用户下载我们的文件了,不过,一旦通过了站点的身份验证,这种保护就形同虚设了。

内容导航

  八、更加具体的保护措施

  我们的目标是,让授权用户能够浏览一个包含了已经购买的软件的页面,并且通过单击其中的链接就可以下载特定的商品。列出产品时,可以使用表结构,但是如何来保护链接呢? 之前我们介绍的方法只是防止匿名用户下载压缩文件,但是现在我们要防止授权用户直接浏览压缩文件。为此,我们需要编写一个定制的处理程序。

  定制的HTTP处理程序是实现了IHttpHandler接口的类。这个接口定义了一个称为ProcessRequest的方法,以及一个Boolean类型的名为IsReusable的属性。该属性决定了其它请求是否能够利用同一个处理程序,所以这里简单返回一个真值。这个方法将会收到一个HttpContext类型的参数。这变量为我们提供了访问该请求整个上下文的权限,包括请求中的信息和订制另一个方向上的请求的方法。

  现在,我们要创建一个称为FileDenialHandler的处理程序,它的作用是停止一个请求,并将用户重定向到一个页面来通知他们访问被拒绝。当这个处理程序取得该请求的时候,就会调用ProcessRequest方法并且执行重定向。



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

public void ProcessRequest(HttpContext context)
   {
   context.Response.Redirect(
      
"~/Downloads/Files/AccessDenied.aspx");
   }

  如您所见,这个页面位于根目录的Downloads/Files文件夹中,完整的FileDenialHandler.cs处理程序如下所示:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
namespace DotNetDude.Web.UI
{
   
public class FileDenialHandler : IHttpHandler
   {
      
public void ProcessRequest(HttpContext context)
      {
         context.Response.Redirect(
            
"~/Downloads/Files/AccessDenied.aspx");
      }

      
public bool IsReusable
      {
         
get
         {
            
return true;
         }
      }
   }
}

  现在,这个FileDenialHandler类什么也没做,所以必须将其写入站点。为了这样,我们将其放入web.config文件的中。

  文件web.config列出了配置部分的所有特殊处理程序,并为其规定了相应的信息,包括实例化这个处理程序的谓词,匹配要让这个处理程序来处理的文件的通配符路径,以及用于该定制的处理程序的类型定义。在这个例子中,添加的配置部分如下所示:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

   <httpHandlers>
   <add verb="*" path="*.zip" 
   type
="DotNetDude.Web.UI.
   FileProtectionHandler,
   DotNetDude.HttpHandlers"
/>
   </httpHandlers>

  参数type是标准的.NET完全限定名写法,并通过逗号组合了一个组件程序集名称。在实际的Web项目中编写处理程序的时候,可以省去程序集名称。

  现在这个条目将所有的压缩文件请求转发给新的处理程序,因此立即将请求重定向到“Access Denied”页面。即使在本例中跳过该IIS条目,它仍然会照常工作,因为这些事情都交由我们的处理程序了。 然而,我们想要的效果是让系统在判定匿名用户访问非法的ZIP 文件之前,首先把他们导航至登录页面。

  如果检查框架目录中的web.config文件,就会发现一列处理常见文件扩展名用于ASP.NET的处理程序。定义这个文件中的部分的处理程序决定了IIS如何恰当地转发ASPX页面、ASMX Web 服务以及所有其他文件。 这个处理程序列表还定义了哪些文件扩展名是禁用的,例如*.config。事实上,可以利用一个称为HttpForbiddenHandler的处理程序来禁用所有以扩展名.config结尾的文件,并自动显示一个“HTTP 403error Forbidden :Access is denied ”页面。

  所以您可能问,无什么不只用Microsoft提供的处理程序来处理压缩文件呢? 答案是,当然可以用,不过我们需要自己的“access denied”页面,这样通过定制我们自己的拒绝页面,就能跟我们的网站风格保持一致。在某些情况下,我们还想为用户提供更多的信息,甚至向管理员发送“未经授权的尝试”类电子邮件。

  这里只是防止下载所有的压缩文件,但是我们实际想要做的是什么?对了,我们要取得从我们站点上文件下载方式的绝对控制权。我们不想让用户直接浏览压缩文件。通过表结构,我们可建立一个项目、用户列表,以及每个用户购买产品的关系表。因此如果我们有一个用户名和一个产品序列号,就可以通过一个简单的数据库查询来判断这个用户是否购买了这个产品。同时,我们还想让用户只需单击一个链接就能启动查询并确定是否被允许下载文件。 这些功能的确非常令人向往。

内容导航

  九、控制下载

  下面,我们开始讲述如何编写一个用于某些文件请求的处理程序,以及该处理程序的安装方法。我们的处理程序的功能很简单,它只是将请求重定向到其他地方而已。ASP.NET还提供了另外一种文件扩展名即ASHX,它无需安装到web.config文件中。我们可以创建一个以这个扩展名结尾的类,来实现IHttpHandler接口,并就直接导航至该类。实际上它与一个页面非常类似,只是它不会使用Web表单和Code-Behind类,所以它是一种更加简洁的方案。

  现在,我们创建一个新的处理程序称为Download.ashx,并让用户浏览到该处理程序的位置,同时在QueryString参数中规定一些信息。下面的URL就是下载链接:

  ~/Downloads/Download.ashx?Product=101

  这个URL表示下载与产品101有关的文件。用户或者链接可以访问上述URL来试图下载一个文件,这时,该处理程序的ProcessRequest方法就会执行。

  利用标准Forms身份验证对用户身份进行验证,这样就能访问在我们站点上下文中的User对象。请记住,HTTP上下文会传递给该处理程序的ProcessRequest方法,所以能访问到所需的内容。对象User允许我们获得使用User.Identity.Name的授权用户的名称,还有,我们还可以使用该方法访问User表中的用户。访问用户的名称,我们要使用User.Identity.IsAuthenticated的值来检查他们是否已经过身份认证,如果没有,将其重定向到“access denied”。此外,我们还要访问请求的产品号码,代码如下所示:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

context.Request.QueryString["Product"]

  这样,我们就取得了产品号码和用户名。 有了这两者,我们就可以访问UserProducts表并确定这个用户是否购买过这个产品。此外,这个表还存储有该产品的文件名。

  既然有了用户名和产品号码,并通过它们确定除了用户购买情况。如果该用户没有购买相应的产品,我们就将其重定向到前面的处理程序,并返回一个访问拒绝页面。为简洁起见,我们将其重定向到一个告知他们还没有购买过此产品的页面,并告知如何进行购买。

  如果确定该用户购买了这个产品,可以通过ProductFileName字段了解该用户可以查看哪些文件。这里,我们没有存储完整的路径,只是存储了文件名。如果需要,我们可以从web.config中的设置来获得该文件夹,所以最终获得了完整的文件路径和名称,并授权下载。 下面我们通过称为StartDownload方法来完成此任务:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

private void StartDownload(
       HttpContext context, 
string downloadFile)
   {
         context.Response.Buffer 
= true;
         context.Response.Clear();
         context.Response.AddHeader(
            
"content-disposition",
            
"attachment; filename=" + downloadFile);
         context.Response.ContentType 
=
            "application/zip";
         context.Response.WriteFile(
            
"~/Downloads/Files/" + downloadFile);
   }

  这里的ProcessRequest方法会调用StartDownload方法,完整的Download.ashx代码如下所示:



Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

<%@ WebHandler Language="C#" Class="Download" %>
using System;
using System.Web;
public class Download : IHttpHandler 
{
   
public void ProcessRequest (HttpContext context) 
   {
      
if (context.User.Identity.IsAuthenticated)
      {
         
if(context.Request.QueryString["Product"!= null
            && context.Request.QueryString["Product"!= "")
         {
            
int productID = Convert.ToInt16(
               context.Request.QueryString[
"Product"]);
            
string userName = context.User.Identity.Name;
            UserProduct product 
= UserProductFactory.GetProductByUser(
               userName, productID);
            
if(product != null)
               StartDownload(product.FileName);
            
else
               context.Response.Redirect(
                  
"~/Downloads/Files/AccessDenied.aspx");
         }
      }
   }

   
public bool IsReusable
   {
      
get 
      {
         
return false;
      }
   }
   
private void StartDownload(string downloadFile)
   {
      context.Response.Buffer 
= true;
      context.Response.Clear();
      context.Response.AddHeader(
         
"content-disposition"
         
"attachment; filename=" + downloadFile);
      context.Response.ContentType 
= "application/zip";
      context.Response.WriteFile(
         
"~/Downloads/Files/" + downloadFile);
   }
}

  这个方法会收到文件的名称以及HttpContext。由此处,我们将清空响应缓冲区,设置一个新的头部,然后设置内容类型。最后,使用WriteFile方法输出该文件,最终用户会收到一个文件保存或打开窗口。

  注意,使用WriteFile会输出ZIP 文件,而是要Response.Redirect则会把用户重定向到访问拒绝页面。使用这种技术的时候,聪明的用户可以绕过安全检查而直接导航至Download.ashx文件。但即使它们设法直接浏览到ZIP 文件,也会被FileDenialHandler处理程序重定向到访问拒绝页面。

  注意这两种类型处理程序的区别,一个是可以放在外部组件中的标准C#(或者VB.NET)类,如果您需要编写可重用的处理程序的时候,这种类型比较理想,因为您可以把它们编译成一个动态链接库,并在不同的站点之间分享。当然,我们需要在web.config文件中对它们进行注册。对于ASHX类型的处理程序,可以像ASPX页面一样添加到站点中。事实上,我们可以使用其他技术来完成类似Download.ASHX的功能,但是ASHX处理程序是更加简洁的解决方案。

  十、小结

  使用ASP.NET保护文件下载的所有方法中,利用HTTP处理程序是最重要的一种。通过与其他技术相结合,我们不仅能够防止非授权用户下载文件,还能在处理用户下载文件试图时获得绝对控制权,希望本文的内容对您能够有所帮助。