65.9K
CodeProject 正在变化。 阅读更多。
Home

使用基本身份验证进行安全文件下载

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (16投票s)

2006年3月19日

6分钟阅读

viewsIcon

130268

downloadIcon

1631

使用基本身份验证安全下载文件。有趣的是,我们为文件的上传和下载维护了两个独立的入口点。

Sample Image - Secure_File_Download.jpg

引言

安全一直是所有类型应用程序(尤其是Web应用程序)的首要问题。Web应用程序几乎对整个世界开放,并容易受到攻击。大多数Web应用程序都提供文件下载功能,真正的挑战不在于提供此功能,而在于保护此类操作。最近,我处理了一个需要安全文件上传和下载的应用程序,在此期间我进行了广泛的研究,所以我想与世界分享它,使其有价值。

你可能会在互联网上找到几篇关于安全下载的文章,但本文与所有这些文章略有不同,因为它通过为上传和下载提供两个不同的入口点来区分它们。

以下文章描述了如何使用IIS基本身份验证实现安全文件下载。

背景

在我们开始之前,让我们回顾一下并学习一些基础知识。

ASP.NET应用程序有两个独立的身份验证层。这是因为ASP.NET不是一个独立的产品。相反,它是IIS之上的一层。所有请求在交给ASP.NET之前都流经IIS。因此,IIS可以决定拒绝访问,而ASP.NET进程甚至不知道有人请求了特定的页面。以下是IIS和ASP.NET联合身份验证过程的步骤概述:

  1. IIS首先检查传入请求是否来自允许访问该域的IP地址。如果不是,则拒绝请求。
  2. 接下来,如果IIS配置为这样做,它会执行自己的用户身份验证。默认情况下,IIS允许匿名访问,因此请求会自动进行身份验证,但你可以在IIS中根据每个应用程序更改此默认设置。
  3. 如果请求与经过身份验证的用户一起传递给ASP.NET,ASP.NET会检查是否启用了模拟。如果启用了模拟,ASP.NET会像经过身份验证的用户一样行事。如果不是,ASP.NET会使用其自己的配置帐户行事。
  4. 最后,步骤3中的身份用于从操作系统请求资源。如果ASP.NET身份验证可以获取所有必要的资源,它会授予用户的请求,否则会拒绝。资源可以包括的不仅仅是ASP.NET页面本身。你还可以使用.NET的代码访问安全功能将此授权步骤扩展到磁盘文件、注册表项和其他资源。

工作原理

通常,任何Web应用程序都包含一个虚拟目录和一个“上传”文件夹,最终用户从/向其中上传/下载文件。在这里,我玩了一个小把戏:尽管上传和下载发生在同一个文件夹中,但我为它们保留了两个不同的入口点。也就是说,我创建了两个不同的虚拟目录,它们指向同一个物理文件夹。

  • 在ASP.NET应用程序所在的Security(你的Web应用程序)虚拟目录中创建一个名为Security的虚拟目录。
  • 在此文件夹中,创建一个名为“Uploads”(你的应用程序子文件夹,你将文件上传到此文件夹)的文件夹。ASP.NET应用程序将使用通用的文件上传实践将文件上传到此文件夹。
  • 再创建一个名为“Downloads”的虚拟目录,并将其映射到位于“Security”(你的Web应用程序)文件夹中的“Uploads”(你的应用程序子文件夹,你将文件上传到此文件夹)文件夹。此虚拟目录仅用于文件下载,与ASP.NET应用程序或文件上传过程无关。

保持上传文件夹不变,我们为文件创建了两个入口点,一个是通过https:///security/uploads,另一个是通过https:///downloads。尽管这两个虚拟目录都指向同一个文件夹,但根据它们的设置,它们的行为会不同。

配置

  • 将附加的源代码解压缩到磁盘上。
  • 启动IIS。
  • 创建一个名为“Security”的新虚拟目录,并将其映射到源代码。
  • 在IIS中,选择“Security”虚拟目录(上面创建的)。你会在其中找到一个“Uploads”文件夹。右键单击“Uploads”文件夹并选择“属性”。删除“Uploads”文件夹的“读取”访问权限,并仅提供“写入”访问权限。

Sample screenshot

  • 创建一个名为“Downloads”的新虚拟目录,并将“Security”文件夹中存在的“Uploads”文件夹映射到它。
  • 打开downloads虚拟目录属性窗口,并仅授予读取权限,如下图所示:

Sample screenshot

使用代码

根据你的应用程序需求修改web.config文件中的以下参数

//Application uploads folder virtual path
<add key="UploadPath" value="/Security/Uploads" />

//Entry point for downloads folder, a virtual path
<add key="DownloadURL" value="https:///downloads" />

//Windows user name
<add key="BasicAuthenticationUser" value="administrator" />

//Windows user password
<add key="BasicAuthenticationPWD" value="admin$123" />

现在,让我们开始学习...................

什么是基本身份验证 - 当未经验证的请求进入Web服务器时,Web服务器返回HTTP 401响应,提示客户端提供其凭据。客户端重新请求同一资源,并在Base-64编码的HTTP头中传递用户名和密码。(Base-64编码不会加密或保护凭据;它只是确保通过线路发送的字符的格式不会与任何保留字符冲突。)由于凭据以明文形式通过线路发送,因此基本身份验证应仅在使用SSL时使用,因为这确保了HTTP请求的整个内容都已加密。但是,在我们的例子中,我们通过localhost将凭据传递给同一服务器,因此以明文形式传递凭据不会成为问题。

.NET Framework 提供了 `WebClient` 类,该类旨在简化 HTTP 请求过程。它包含用于向 URI 标识的资源发送数据和从该资源接收数据的常用方法。`HttpWebRequest` 类用于生成请求,`HttpWebResponse` 用于从服务器检索响应。通常,`HttpWebRequest` 和 `HttpWebResponse` 可以满足所有目的,但在基本身份验证的情况下,还需要一个额外的类,即 `CredentialCache`

`WebClient` 和 `HttpWebRequest` 类都可以通过其 `Credentials` 属性轻松地在请求中包含身份验证信息。`Credentials` 属性接受实现 `ICredentials` 的对象。`CredentialCache` 类提供凭据存储。`CredentialCache` 类的目的是存储用户的凭据集。当对资源发出请求时,可以查询 `CredentialCache` 类,并根据请求的资源提取适当的凭据。

此类的最简单用法仅涉及几行代码。我们需要执行的步骤包括:

  1. 创建该类的实例。
  2. 调用`DownloadData`方法,传入URL(它返回一个`Byte`数组)。
  3. 使用`Response.BinaryWrite`写入下载数据的`Byte`,这反过来会提示用户下载文件。

以下代码展示了如何使用`CredentialCache`类和`WebClient`的`Credentials`属性向受基本身份验证保护的URL发出请求

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Net;
using System.IO;
namespace security
{
    /// <SUMMARY>
    /// Summary description for SecureFile.
    /// </SUMMARY>
    public class SecureFile
    {
        public SecureFile()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        public bool UploadFile(HtmlInputFile inputfile)
        {
            try
            {
                string fileName = "";
                string DirPath = "";
            
                if(( inputfile.PostedFile != null ) && 
                   ( inputfile.PostedFile.ContentLength > 0 ))
                {
                    DirPath=HttpContext.Current.Server.MapPath(
                      System.Configuration.ConfigurationSettings.AppSettings[
                      "UploadPath"]);
                    fileName = System.IO.Path.GetFileName(
                                     inputfile.PostedFile.FileName );
                    inputfile.PostedFile.SaveAs( DirPath + "\\" + fileName  );
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool DownloadFile(string strFile)
        {
            try
            {
                string strDownloadURL=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "DownloadURL"];
                string strUser=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "BasicAuthenticationUser"];
                string strPWD=
                  System.Configuration.ConfigurationSettings.AppSettings[
                  "BasicAuthenticationPWD"];
                string strURL=strDownloadURL + "\\" + strFile;
                 //Creating an instance of a WebClient
                WebClient req=new WebClient();

                //Creating an instance of a credential cache, 
                //and passing the username and password to it
                CredentialCache mycache=new CredentialCache();
                mycache.Add(new Uri(strURL),"Basic", 
                            new NetworkCredential(strUser,strPWD));
                req.Credentials=mycache;

                //Creating an instance of a Response object
                HttpResponse response = HttpContext.Current.Response;
                response.Clear();
                response.ClearContent();
                response.ClearHeaders();
                response.Buffer= true;

                //Keep the current page as it is, and writes 
                //the content to an new instance, 
                //which prompts the user to download the file 
                response.AddHeader("Content-Disposition", 
                  "attachment;filename=\"" + strFile + "\"");
                byte[] data=req.DownloadData(strURL);
                response.BinaryWrite(data);
                response.End();
                return true;
            }
            catch(Exception ex)
            {
                if(ex.Message=="The remote server " + 
                               "returned an error: (404) Not Found.")
                    throw new Exception("File not found");
                else if(ex.Message=="The remote server" + 
                        " returned an error: (401) Unauthorized.")
                    throw new Exception("Unauthorized access");

                return false;
            }
        }
    }
}

尽情享受!!!任何反馈都将不胜感激。

使用代码

为了快速实现和演示目的,我使用了管理员用户,但我强烈建议你创建一个新用户,该用户仅对“Uploads”文件夹具有“写入”权限,并且在服务器/系统上没有其他权限。

© . All rights reserved.