ASP.NET入门之Web 应用程序的基本安全实施策略

来源:互联网 发布:js 组织架构图控件 编辑:程序博客网 时间:2024/05/06 21:27
常规 Web 应用程序安全性建议


有些最基本的安全性建议也是最显然易见的。但是,如果恶意用户可以使用简单方法进入您的计算机,即使是最精心设计的应用程序安全性也会失败。

经常进行备份,并将备份存放在安全的场所。
将您的 Web 服务器计算机放置在安全的场所,这样,未经授权的用户就无法使用它、关闭它、带走它,等等。
使用 Windows NTFS 文件系统,不使用 FAT32。NTFS 的安全性比 FAT32 高得多。有关详细信息,请参见 Windows 文档。
使用不易破解的密码,确保 Web 服务器计算机和同一网络上的所有计算机的安全。
确保 IIS 的安全。有关详细信息,请参见 Microsoft 安全性 Web 站点 (http://microsoft.com/technet/treeview/default.asp?url=/technet/security/bestprac/mcswebbp.asp) 上的文章“管理 Windows IIS Web 服务的安全性”。

关闭不使用的端口并关闭不使用的服务。
运行监视通信量的病毒检查程序。
建立并实施以下策略:禁止用户将其密码记在容易找到的位置。
使用防火墙。有关建议,请参见 Microsoft 安全性站点上的“核对清单:安装防火墙”。(http://microsoft.com/security/articles/firewall.asp)
了解和安装来自 Microsoft 和其他供应商的最新安全修补程序。例如,Microsoft 安全站点 (www.microsoft.com/security) 中有所有 Microsoft 产品的最新安全公告的列表。其他供应商也有类似站点。
使用 Windows 事件日志记录,并且经常检查这些日志,以查找可疑活动。这样的活动包括:反复尝试登录您的系统,以及向您的 Web 服务器发出数量巨大的请求。


使用最少特权运行应用程序



当您的应用程序运行时,它运行在一个具有本地计算机(还可能是远程计算机)的特定特权的上下文中。请遵循这些指导:

在具有最少实用特权的上下文中运行应用程序。默认情况下,在 IIS 5 版中,ASP.NET 应用程序运行在名为 ASPNET 的本地用户的上下文中。在 Windows Server 2003 上的 IIS 6 上,此帐户称为 NETWORK SERVICE。有关详细信息,请参见 ASP.NET 进程标识和配置 ASP.NET 进程标识。
不要在系统用户(管理员)的上下文中运行应用程序。
设置应用程序所需的所有资源上的权限(ACL 或访问控制列表)。使用最少的容许设置。例如,如果在您的应用程序中是可行的,则将文件设置为只读。有关详细信息,请参见 Windows 文档。
将您的 Web 应用程序的文件保存在应用程序根目录下的一个文件夹中。不要让用户指定在应用程序中进行文件访问的路径。这样有助于防止用户访问服务器的根目录。
不要使不需要调试 Visual Studio 应用程序的任何人成为“Debugger Users”(调试器用户)组的成员。此组具有比“VS Developers”(VS 开发人员)组更高的特权。有关详细信息,请参见添加调试器用户和配置 DCOM。


了解您的用户



在许多应用程序中,用户有可能不必提供凭据即可访问站点。如果是这样,则您的应用程序通过在预定义用户的上下文中运行即可访问资源。默认情况下,此上下文是 Web 服务器计算机上的本地 ASPNET 用户(Windows 2000 或 Windows XP)或 NETWORK SERVICE 用户 (Windows Server 2003)。

若要仅允许已授权用户进行访问,请遵循以下准则:

如果您的应用程序是 Intranet 应用程序,则将其配置为使用 Windows 集成安全性。这样,用户的登录凭据就可以用于访问资源。
如果您需要从用户收集凭据,则使用其中的一种 ASP.NET 身份验证策略。有关更多信息,请参见 ASP.NET 身份验证。有关示例,请参见简单 Forms 身份验证。


防止恶意用户的输入



通常,决不假定从用户获得的输入是安全的。对恶意用户来说,从客户端向您的应用程序发送潜在危险的信息是很容易的。若要防止恶意输入,请遵循以下准则:

在窗体中,筛选用户输入以查找 HTML 标记,其中可能包含脚本。有关详细信息,请参见在 Web 应用程序中防止脚本利用。
决不回显(显示)未经筛选的用户输入。在显示不受信任的信息之前,对 HTML 进行编码以将潜在有害的脚本转换为显示字符串。
类似地,决不将未经筛选的用户输入存储在数据库中。
如果要接受来自用户的一些 HTML,则手动筛选它。在您的筛选器中,显式定义将要接受的内容。不要创建一个试图筛选出恶意输入的筛选器;因为预料到所有可能的恶意输入是非常困难的。
不要假定您从标头(通常通过 Request 对象)获得的信息是安全的。对查询字符串、Cookie 等采取安全措施。注意浏览器向服务器报告的信息(用户代理信息)可以假冒,假如它在您的应用程序中是重要的。
如有可能,不要将敏感信息(如隐藏域或 Cookie)存储在可从浏览器访问的位置。例如,不要将用户名或密码存储在 Cookie 中。
注意 视图状态是以编码格式存储在隐藏域中的。默认情况下,它包含消息身份验证代码 (MAC),以便页可以确定视图状态是否已被篡改。



创建安全的错误信息



如果您不小心,恶意用户就可以从应用程序显示的错误信息推断出有关您的应用程序的重要信息。请遵循这些指导:

不要编写会回显可能对恶意用户有用的信息(例如用户名)的错误信息。
将应用程序配置为不向用户显示详细错误。如果为进行调试而要显示详细错误信息,请先检查该用户是否为 Web 服务器的本地用户。有关详细信息,请参见显示安全的错误信息。
对于容易发生错误的情况(如数据库访问)创建自定义错误处理方式。


安全地保守秘密



“秘密”是指您不希望某人知道的信息。一个典型的秘密是密码或加密密钥。当然,如果恶意用户能够获得秘密,则由该秘密保护的数据会受到危害。请遵循这些指导:

如果您的应用程序在浏览器和服务器之间传输敏感信息,请考虑使用安全套接字层 (SSL)。有关如何实现 SSL 的详细信息,请参见 Microsoft 知识库文章 Q307267“HOW TO:在 Windows 2000 中使用安全套接字层来保障 XML Web services 的安全”(HOW TO: Secure XML Web Services with Secure Socket Layer in Windows 2000)。
如果您必须存储秘密,即使是以您认为人们将无法看到它的形式(如在服务器代码中)进行保存,也不要将它保存在 Web 页中。
如果您对信息加密,请不要创建您自己的加密算法。请使用 Windows 数据保护 API (DPAPI)。


安全地使用 Cookie



为了让用户特定的信息保持可用,Cookie 是一种容易而有用的方法。但是,由于 Cookie 会被发送到浏览器所在的计算机,因此它们容易被假冒或用于其他恶意用途。请遵循这些指导:

不要将任何关键信息存储在 Cookie 中。例如,不要将用户的密码存储在 Cookie 中,即使是暂时存储也不要这样做。通常,不要将任何信息保存在 Cookie 中(一旦它被假冒,就会给您带来麻烦),而是在 Cookie 中保存对信息在服务器上的位置的引用。
设置 Cookie 的到期日期,使其有效期尽可能最短。如有可能,避免设置永久性 Cookie。
考虑对 Cookie 中的信息加密。


防止拒绝服务威胁



恶意用户危害您的应用程序的一种间接方式是使其不可用。恶意用户可以使应用程序太忙而无法为其他用户提供服务,或者仅仅使应用程序出现故障。请遵循这些指导:

关闭或释放您使用的任何资源。例如,在使用完毕后,始终关闭数据连接和数据读取器,而且始终关闭文件。
使用错误处理(例如,try-catch)。包含 finally 块,以便万一失败就可以在其中释放资源。
将 IIS 配置为使用进程调节,这样可以防止应用程序消耗过多的 CPU 时间。有关详细信息,请参见技术文章“使用 Internet Information Server 5.0 的新功能:第 1 部分”。
在使用或存储用户输入之前,测试它的大小限制。
对数据库查询的大小加以限制以确保安全。例如,在 Web 窗体页中显示查询结果之前,检查包含的记录数是否不合理。
如果文件上载是您的应用程序的一部分,则对它们的大小加以限制。您可以使用类似如下的语法在 Web.config 文件中设置限制(其中 maxRequestLength 值以千字节为单位):
<configuration>
<system.web>
<httpRuntime maxRequestLength="4096" />
</system.web>
</configuration>

脚本利用

从浏览器的角度来看,Web 页只是一个长字符串。浏览器会顺序处理这个字符串,在此过程中,会显示某些字符,同时按特殊规则解释其他字符(如 <b> 和 <script>)。如果恶意用户可以将某些特殊字符插入到页中,则浏览器将不知道这些字符不应该处于该位置,将作为页的一部分处理它们。

一个简单化的脚本利用的工作方式如下所示。假定一个应用程序允许用户发送有关最新电影的评论以供其他用户阅读。利用步骤可能为:

应用程序显示一个用户可以输入评论的窗体。恶意用户编写了一个其中包含 <script> 块的评论。
发送窗体,恶意用户的评论将存储在数据库中。
另一用户访问该站点。在构造页时,应用程序会从数据库中读取评论并将它们放在页中。恶意用户的 <script> 块将写入页中,就好像它是文本评论一样。
当第二个用户的浏览器显示此页时,它将遇到 script 块并执行它。
恶意用户还可以使用其他方法来利用脚本。常见的情况是:大多数脚本利用都会要求应用程序接受恶意输入,并将其插入到页中(回显它),浏览器将在该页中执行它。这种利用带来的潜在损害取决于所执行的脚本。它可以是无足轻重的,如在浏览器中弹出的烦人的消息。但是,它也可以产生严重的损害,方法是偷窃 Cookie、偷窃用户输入(如密码),甚至在用户的计算机上运行本机代码(如果对 Internet 安全性的要求不严格)。

防止脚本利用

防止脚本利用的主要方法就是决不信任来自用户的信息。假定从浏览器发送到您的应用程序的任何数据都包含恶意脚本。

同样,每次将字符串写入页时,您都应该假定字符串可能包含恶意脚本。(除非您自己以编程方式创建了该字符串。)例如,在从数据库中读取字符串时,您应该假定它们可能包含恶意脚本。安全意识很强的开发人员甚至不信任他们自己的数据库,理由是他们认为恶意用户可能有办法篡改数据库。

ASP.NET 为您提供了几种防止脚本利用的方法:

ASP.NET 通过请求验证来自动防止脚本利用。默认情况下,如果 Request 对象包含 HTML 编码的元素或某些 HTML 字符(如表示长破折号的 &#151;),则 ASP.NET 页框架将引发一个错误。建议您为此错误在应用程序中创建一个处理程序。
如果您要在应用程序中显示字符串,但不信任它们,则向它们应用 HTML 编码。例如,进行编码后,标记 <b> 将变成 &lt;b&gt;。如果您正在显示的字符串来自您尚未确定信任其内容的数据库时,您可能会这样做。
如果您希望应用程序接受某些 HTML(例如,来自用户的某些格式设置指令),请关闭自动检查,并创建筛选器来精确定义应用程序将接受哪些 HTML。
安全说明 决不要禁用自动请求验证而不添加您自己的检查或筛选器。
注意 不要创建试图只筛选出不可接受元素的筛选器,因为预料每个可能的错误输入十分困难。相反,如果您确实要创建筛选器,请创建具有可接受输入的已定义列表的筛选器。

在 Web 应用程序中防止脚本利用

大多数脚本利用发生在用户可以将可执行代码(脚本)插入您的应用程序时。默认情况下,ASP.NET 提供请求验证。不管窗体发送包含什么样的 HTML,该验证都会引发错误。您可以使用下列方法防止脚本利用: 在接受或显示字符串之前,将 HTML 编码应用于它们,以便字符串不包括任何可执行元素。 如果您的应用程序需要接受某些 HTML,则禁用请求验证并创建您自己的 HTML 筛选器。 本主题中的过程说明如何执行这些任务。应用 HTML 编码HTML 编码使用 HTML 保留字符转换 HTML 元素,以便显示它们而不是执行它们。应用 HTML 编码 在显示字符串之前,调用 Server 对象的 HtmlEncode 方法。HTML 元素会转换为浏览器将显示(而不解释为 HTML)的字符串表示形式。 以下示例说明 HTML 编码。在一个实例中,在显示用户输入之前对其进行编码。在第二个实例中,在显示数据库中的数据之前对其进行编码。 注意   只有通过向 @ Page 指令中添加 alidateRequest="false" 来在页中禁用请求验证时,此示例才将起作用。决不禁用请求验证而不添加您自己的检查或筛选器。' Visual BasicPrivate Sub Button1_Click(ByVal sender As System.Object, ByVal e _      As System.EventArgs) Handles Button1.Click   Label1.Text = Server.HtmlEncode(TextBox1.Text)   Label2.Text = _       Server.HtmlEncode(dsCustomers.Customers(0).CompanyName)End Sub// C#private void Button1_Click(object sender, System.EventArgs e){    Label1.Text = Server.HtmlEncode(TextBox1.Text);    Label2.Text =         Server.HtmlEncode(dsCustomers1.Customers[0].CompanyName);}筛选 HTML 元素默认情况下,Web 窗体页检测发送到服务器的信息中的任何 HTML 元素和保留字符。这样,将防止用户试图将脚本嵌入您的应用程序。当页检测到 HTML 时,它会引发一个错误。您可以使用 Page_Error 或 Application_Error 处理程序捕捉此错误。有关详细信息,请参见显示安全的错误信息。但是,如果您的应用程序需要接受某些 HTML 元素,可关闭请求验证,并创建一个只允许使用要接受的 HTML 元素的筛选器。注意   不要创建试图只筛选出不可接受元素的筛选器,因为预料每个可能的错误输入十分困难。相反,如果您创建筛选器,则创建一个定义可接受输入的筛选器。筛选 HTML 元素 通过将属性 ValidateRequest="false" 添加到 @ Page 指令中禁用请求验证。 安全说明   决不要禁用自动请求验证而不添加您自己的检查或筛选器。使用 HtmlEncode 方法对字符串进行编码。 调用 String.Replace 方法,将要接受的已编码 HTML 标记转换回它们的 HTML 形式。 提示   如果您熟悉正则表达式,则可以使用一个正则表达式来高效地执行筛选。有关详细信息,请参见 .NET Framework 正则表达式。以下示例说明一个简单的筛选器,它接受加粗元素和带下划线的元素(<b>、</b>、<u>、</u>)。在显示所有其他用户输入之前都对其进行编码。 安全说明   许多 HTML 标记都允许在其属性中使用脚本。例如,标记 <img src="javascript:alert('hi')"> 是合法的。如果您希望接受比简单的格式设置标记更复杂的 HTML 标记,则必须确保恶意用户无法假借允许的 HTML 标记将脚本传递到您的应用程序。' Visual BasicPrivate Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click   Dim userinput As String = TextBox1.Text   userinput = Server.HtmlEncode(userinput)   ' Accepts <b>, </b>, <u>, </u>, case-insensitive   userinput = userinput.Replace("&lt;b&gt;", "<b>")   userinput = userinput.Replace("&lt;/b&gt;", "</b>")   userinput = userinput.Replace("&lt;B&gt;", "<B>")   userinput = userinput.Replace("&lt;/B&gt;", "</B>")   userinput = userinput.Replace("&lt;u&gt;", "<u>")   userinput = userinput.Replace("&lt;/u&gt;", "</u>")   userinput = userinput.Replace("&lt;U&gt;", "<U>")   userinput = userinput.Replace("&lt;/U&gt;", "</U>")   Label1.Text = userinputEnd Sub// C#private void Button1_Click(object sender, System.EventArgs e){    String userinput = TextBox1.Text;    userinput = Server.HtmlEncode(userinput);    // Accepts <b>, </b>, <u>, </u>, case-insensitive    userinput = userinput.Replace("&lt;b&gt;", "<b>");    userinput = userinput.Replace("&lt;/b&gt;", "</b>");    userinput = userinput.Replace("&lt;B&gt;", "<B>");    userinput = userinput.Replace("&lt;/B&gt;", "</B>");    userinput = userinput.Replace("&lt;u&gt;", "<u>");    userinput = userinput.Replace("&lt;/u&gt;", "</u>");    userinput = userinput.Replace("&lt;U&gt;", "<U>");    userinput = userinput.Replace("&lt;/U&gt;", "</U>");    Label1.Text = userinput;}



SQL 语句利用

还有一种与脚本利用类似的利用,它导致恶意 SQL 语句的执行。如果应用程序提示用户输入信息并将用户的输入串联为表示 SQL 语句的字符串,则会出现这种情况。例如,应用程序可能提示输入客户姓名,目的是为了执行类似如下的语句:

"Select * From Customers where CustomerName = " & txtCustomerName.Value
但是,对数据库有所了解的恶意用户可能使用文本框输入包含客户姓名的嵌入式 SQL 语句,产生类似如下的语句:

Select * From Customers Where CustomerName = 'a' Delete From
Customers Where CustomerName > ''
执行该查询时,就会危害数据库。

为了防止 SQL 语句利用,决不使用字符串串联创建 SQL 查询。相反,使用参数化查询并将用户输入分配给参数对象。

数据适配器命令中的参数

数据适配器的命令(在 SelectCommand、InsertCommand、UpdateCommand 和 DeleteCommand 对象的 CommandText 属性中定义)经常涉及参数。在运行时,参数用于向命令代表的 SQL 语句或存储过程传递值。参数在两种上下文中使用: 选择参数 - 在产品应用程序中,经常只获取数据库中数据的一个子集。其做法是,使用包含 WHERE 子句的 SQL 语句或存储过程,该子句具有用于获得选择判据(在运行时获取)的参数。此外,当更新或删除记录时,将使用 WHERE 子句指出要更改的一条或多条记录。WHERE 子句中使用的值通常在运行时导出。 更新参数 - 当更新现有记录或插入新记录时,已更改记录或新记录中列的值将在运行时建立。此外,开放式并发检查中使用的值也使用参数来建立。 注意   对于 Oracle,在 SQL 语句或存储过程中使用命名参数时,必须在参数名称前加冒号 (:)。但是,当在代码中的其他地方引用命名参数时(例如,当调用 Add 时),不要在命名参数前加冒号 (:)。数据提供程序自动提供冒号。有关更多信息,请参见 OracleParameter 类。选择参数当选择记录来填充数据集时,经常在 WHERE 子句中包括一个或多个参数,以便能够在运行时指定要获取哪些记录。例如,用户可能会搜索书籍数据库以查找其键入 Web 页的特定书名关键字。为了允许该操作,可以指定如下 SQL 语句作为 SelectCommand 的 CommandText 属性。参数用占位符(问号)或命名参数变量指示。涉及 OleDbCommand 和 OdbcCommand 对象的查询的参数使用问号;使用 SqlCommand 对象的查询使用以 @ 符号开头的命名参数,而使用 OracleCommand 对象的查询使用以冒号 (:) 开头的命名参数。使用占位符的查询可能如下所示:SELECT BookId, Title, Author, Price from BOOKSWHERE (Title LIKE ?)使用 SqlCommand 命名参数的查询可能如下所示:SELECT BookId, Title, Author, Price from BOOKSWHERE (Title LIKE @title)使用 OracleCommand 命名参数的查询可能如下所示:SELECT BookId, Title, Author, Price from BOOKSWHERE (Title LIKE :title)在应用程序中,您提示用户输入书名关键字。然后设置参数值并运行命令。注意   有时,您可能需要获取数据库表的全部内容(例如在建立查找表时),但通常只需要获取所需数据以使应用程序效率较高。在 Visual Studio 中,可以使用“查询生成器”生成带参数的 SQL 语句。如果从“服务器资源管理器”拖动元素,Visual Studio 可在某些情况下(但非所有情况下)配置参数,并且您需要手动完成配置。更新参数无论适配器的 SelectCommand 对象是否包含参数化命令,UpdateCommand、InsertCommand 和 DeleteCommand 属性的命令始终包含参数。UpdateCommand 和 InsertCommand 属性的命令对于数据库中要更新的每一列都需要参数。此外,UpdateCommand 和 DeleteCommand 语句需要参数化的 WHERE 子句,它标识要更新的记录,其方式与经常配置 SelectCommand 对象的方式类似。设想用户可以用其购买书籍的应用程序。用户在购物时将维护一个购物车(以数据表的形式实现)。在 ShoppingCart 表中,用户为要购买的每本书维持一条记录,以书籍 ID 和客户 ID 一起作为购物车记录的键。当用户向购物车添加书籍时,应用程序可能调用 SQL INSERT 语句。在适配器中,该语句的语法可能如下所示:INSERT INTO ShoppingCart   (BookId, CustId, Quantity)Values (?, ?, ?)三个问号代表参数占位符,它们将在运行时以客户 ID、书籍 ID 和数量的值填写。如果打算使用命名参数,同样的查询可能如下所示:INSERT INTO ShoppingCart   (BookId, CustId, Quantity)Values (@bookid, @custid, @quantity)如果用户决定更改购物车中的某一项(例如更改其数量),则应用程序可能调用 SQL UPDATE 语句。该语句的语法可能如下所示:UPDATE ShoppingCart   SET (BookId = ?, CustId = ?, Quantity = ?)WHERE (BookId = ? AND CustId = ?)或者如果打算使用命名参数,则可能如下所示:UPDATE ShoppingCart   SET (BookId = @bookid, CustId = @custid, Quantity = @quantity)WHERE (BookId = @bookid AND CustId = @custid)在该语句中,SET 子句中的参数以已更改记录的更新值填写。WHERE 子句中的参数标识要更新哪条记录,并且以来自该记录的原始值填写。用户还可以从购物车移除项。在此情况下,如果打算使用参数占位符,则应用程序可能调用具有如下语法的 SQL DELETE 语句:DELETE FROM ShoppingCartWHERE (BookId = ? AND CustId = ?)或者如果打算使用命名参数,则可能如下所示:DELETE FROM ShoppingCartWHERE (BookId = @bookid AND CustId = @custid)参数集合和参数对象为允许您在运行时传递参数值,数据适配器的四个命令对象均支持 Parameters 属性。该属性包含单个参数对象的集合,这些对象与语句中的占位符一一对应。下表显示了与每个数据适配器相对应的参数集合:数据适配器 参数集合 SqlDataAdapter SqlParameterCollection OleDbDataAdapter OleDbParameterCollection OdbcDataAdapter OdbcParameterCollection OracleDataAdapter OracleParameterCollection 注意   对于 Oracle,在 SQL 语句或存储过程中使用命名参数时,必须在参数名称前加冒号 (:)。但是,当在代码中的其他地方引用命名参数时(例如,当调用 Add 时),不要在命名参数前加冒号 (:)。Oracle .NET Framework 数据提供程序会自动提供冒号。使用参数集合,您可省去必须手动将 SQL 命令构造为具有运行时值的字符串的麻烦。此外,还获得在参数中进行类型检查的好处。如果使用“数据适配器配置向导”配置适配器,则为所有四个适配器命令自动建立和配置参数集合。如果从“服务器资源管理器”将元素拖动到窗体或组件上,则 Visual Studio 可以执行下列配置: 如果将表或某些列拖动到设计器上,则 Visual Studio 将生成一个不带参数的 SelectCommand 对象(明确地说是一条 SQL SELECT 语句),以及参数化的 UpdateCommand、InsertCommand 和 DeleteCommand 对象。如果希望 SelectCommand 对象语句有参数,则必须手动进行配置。 如果将存储过程拖动到设计器上,则 Visual Studio 将生成一个 SelectCommand 对象,带有存储过程所需的参数。但是,如果需要,您必须自己配置 UpdateCommand、InsertCommand 和 DeleteCommand 对象及其参数。 一般而言,如果想为适配器创建参数化查询,则应使用“数据适配器配置向导”。但是,如果需要,可以使用“属性”窗口手动配置参数。参数集合的结构命令的参数集合中的项与相应的命令对象所需的参数一一对应。如果命令对象是一条 SQL 语句,则集合中的项对应于该语句中的占位符(问号)。以下 UPDATE 语句需要有五个参数项的集合:UPDATE ShoppingCart   SET (BookId = ?, CustId = ?, Quantity = ?)WHERE (BookId = ? AND CustId = ?)以下是带有命名参数的相同语句:UPDATE ShoppingCart   SET (BookId = @bookid, CustId = @custid, Quantity = @quantity)WHERE (BookId = @bookid AND CustId = @custid)如果命令对象引用的是存储过程,则集合中的参数项数由该过程本身决定。参数与 SQL 语句中的占位符可能不完全对应。在存储过程中,还可以对参数进行命名。在此情况下,参数在集合中的位置并不重要。相反,集合中的每个参数项都有一个 ParameterName 属性,用于使自身与存储过程中的相应参数匹配。当手动配置参数集合时,必须确切理解哪些参数是存储过程需要的。许多存储过程返回值;如果是这样,该值在参数集合中传递回应用程序,因此您必须允许返回值。此外,某些存储过程包含多条 SQL 语句,您必须确保参数集合反映传递给过程中的全部语句的所有值。如果参数不是命名参数(如在存储过程中那样),则集合中的项按位置映射到命令所需的参数。如果命令是一个存储过程并且返回值,则为该返回值保留集合中的第一项(零项)。因此,可以根据集合中的索引位置引用单个参数对象。但是,参数对象还支持 ParameterName 属性,它提供一种独立于参数顺序来引用参数的方法。例如,下面两条语句可能等效(假定集合中的第二个参数被命名为 Title_Keyword):' Visual Basic' Encloses the keyword in SQL wildcard characters.titleKeyword = "%" & txtTitleKeyword.Text & "%"OleDbDataAdapter1.SelectCommand.Parameters(1).Value = titleKeywordOleDbDataAdapter1.SelectCommand.Parameters("Title_Keyword").Value = titleKeyword// C#// Encloses the keyword in SQL wildcard characters.string titleKeyword = "%" + txtTitleKeyword.Text + "%";this.OleDbDataAdapter1.SelectCommand.Parameters[1].Value = titleKeyword;this.OleDbDataAdapter1.SelectCommand.Parameters["Title_Keyword"].Value = titleKeyword;编程时使用参数名通常比通过索引值引用参数好得多,因为它减少了在参数个数变化时的维护需要,并使您不必记忆存储过程是否返回值。通过名称而不是索引值来引用参数存在少许附加系统开销,但这可通过编程方便和应用程序的可维护性抵消。建立参数值建立参数值的方式有两种: 通过显式设置参数的 Value 属性。 通过将参数映射到数据集表中的列,以便可以在需要时从数据行提取值。 在填充数据集或调用命令时显式设置参数值(即对于选择参数)。例如,在上面搜索书籍的示例中,应用程序可能有一个文本框,用户在该框中输入书名关键字。然后,您在调用适配器的 Fill 方法之前,将参数的值显式设置为文本框的文本。完成该操作的代码可能如下所示,它在填充数据集之前将文本框的内容建立为参数。' Visual Basic' Encloses the keyword in SQL wildcard characters.titleKeyword = "%" & txtTitleKeyword.Text & "%"OleDbDataAdapter1.SelectCommand.Parameters("Title_Keyword").Value = titleKeywordOleDbDataAdapter1.Fill(dsAuthors1)// C#// Encloses the keyword in SQL wildcard characters.titleKeyword = "%" + txtTitleKeyword.Text + "%";this.OleDbDataAdapter1.SelectCommand.Parameters["Title_Keyword"].Value = titleKeyword;this.OleDbDataAdapter1.Fill(dsAuthors1);映射的参数值在更新中使用。当调用适配器的 Update 方法时,该方法依次处理数据集表中的记录,分别为每条记录进行适当更新(更新、插入或删除)。在此情况下,参数值已可以作为数据集记录中的列使用。例如,当更新进程到达数据集表中的一个新记录(必须为其在数据库中调用 INSERT 语句)时,INSERT 语句的 VALUE 子句的值可以从该记录直接读出。这些是典型方案,但不是唯一的几个方案。存储过程有时使用 out 参数或通过过程的返回值返回数据。如果是这样,返回值应映射到数据集表中的列。也有可能显式设置更新参数。适配器支持 RowUpdating 事件,每次更新行时都将调用该事件。可以为该事件创建一个处理程序,并在其中设置参数值。这使您可以对参数值进行十分精确的控制,并可以执行诸如下面的处理:在参数值写入数据库记录之前动态创建参数值。显示安全的错误信息

在您的应用程序显示错误信息时,它不应该泄露有助于恶意用户攻击您系统的信息。例如,如果您的应用程序试图登录数据库时没有成功,则显示的错误信息不应该包括它正在使用的用户名。有许多方法可以控制错误信息: 将应用程序配置为不向远程(应用程序)用户显示详细错误信息。您也可以选择将错误重定向到应用程序页。 只要可行就包括错误处理,并编写您自己的错误信息。在您的错误处理程序中,您可以进行测试以确定用户是否为本地用户并作出相应的响应。 在捕捉所有未处理异常并将它们发送到一般错误页的页级别或应用程序级别上,创建全局错误处理程序。这样,即使您没有预料到某个问题,至少用户不会看到异常页。 将应用程序配置为不向远程用户显示错误 在应用程序的 Web.config 文件中,对 customErrors 元素进行以下更改: 将 mode 属性设置为 RemoteOnly(区分大小写)。这就将应用程序配置为仅向本地用户(您和开发人员)显示详细的错误。 (可选)包括指向应用程序错误页的 defaultRedirect 属性。 (可选)包括将错误重定向到特定页的 <error> 元素。例如,您可以将标准 404 错误(未找到页)重定向到您自己的应用程序页。 以下示例显示 Web.config 文件中的典型 customErrors 块。 <customErrors mode="RemoteOnly" defaultRedirect="AppErrors.aspx">    <error statusCode="404" redirect="NoSuchPage.aspx"/>    <error statusCode="403" redirect="NoAccessAllowed.aspx"/> </customErrors> 包括错误处理 在可能产生错误的任何语句前后使用 try-catch-finally 块。 (可选)使用 Context 对象的 UserHostAddress 属性对本地用户进行测试并相应地修改错误处理。值 127.0.0.1 等效于“localhost”并指示浏览器与 Web 服务器位于同一台计算机上。 下面显示的是一个示例错误处理块。如果发生错误,则用有关消息的详细信息加载 Session 状态变量,然后应用程序显示可以读取 Session 变量并显示错误的页。(有意写入此错误以便不向用户提供任何可利用的详细信息。)如果用户是本地用户,则提供不同的错误详细信息。在 finally 块中,释放开放式资源。 ' Visual BasicTry   SqlConnection1.Open()   SqlDataAdapter1.Fill(Me.DsPubs1)Catch ex As Exception   If HttpContext.Current.Request.UserHostAddress = "127.0.0.1" Then      Session("CurrentError") = ex.Message    Else      Session("CurrentError") = "Error processing page."    End If    Server.Transfer("ApplicationError.aspx")Finally       SqlConnection1.Close()End Try// C#try{    sqlConnection1.Open();    sqlDataAdapter1.Fill(dsCustomers1);}catch (Exception ex){    if(HttpContext.Current.Request.UserHostAddress == "127.0.0.1")    { Session["CurrentError"] = ex.Message; }    else    { Session["CurrentError"] = "Error processing page."; }    Server.Transfer("ApplicationError.aspx");}finally {    this.sqlConnection1.Close();}您也可以创建一个这样的错误处理程序,它在页级别上或为整个应用程序捕捉所有未处理的异常。创建全局错误处理程序 要创建页中的全局处理程序,请创建 Page_Error 事件的处理程序。要创建应用程序范围的错误处理程序,请在 Global.asax 文件中将代码添加到 Application_Error 方法。只要您的页或应用程序中发生未处理的异常,就会调用这些方法。您可以从 HttpServerUtility.GetLastError 方法获取有关最新错误的信息。 注意   如果您具有全局错误处理程序,则它优先于在 Web.config customErrors 元素的 defaultRedirect 属性中指定的错误处理。下面显示的是一个示例处理程序,它获取有关当前错误的信息,将其放在 Session 变量中,并调用可以提取和显示错误信息的一般错误处理页。 ' Visual BasicSub Application_Error(ByVal sender As Object, ByVal e As EventArgs)   Session("CurrentError") = "Global: " & Server.GetLastError.Message   Server.Transfer("lasterr.aspx")End Sub// C#protected void Application_Error(Object sender, EventArgs e){    Session["CurrentError"] = "Global: " +         Server.GetLastError().Message;    Server.Transfer("lasterr.aspx");}