构建安全的数据访问组件

面的代码显示了 CheckProductStockLevel 方法(用来在产品数据库中查询库存量)的示例实现,该代码阐释了本模块前面介绍的数据访问代码的许多重要安全功能。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using Microsoft.Win32;
using DataProtection;

public static int CheckProductStockLevel(string productCode)
{
int quantity = 0;
// (1) 由 try/catch 块保护的代码
try
  {
// (2) 使用正则表达式验证的输入内容
//     应该从资源程序集中检索错误消息,以帮助实现
//     本地化。为简短起见,省略了 Localization(本地化)代码。
if (Regex.IsMatch(productCode, "^[A-Za-z0-9]{12}$") == false)
throw new ArgumentException("Invalid product code" );
//(3) using 语句确保连接被断开
using (SqlConnection conn = new SqlConnection(GetConnectionString()))
    {
// (4) 使用参数化存储过程可以应对
//     SQL 注入攻击
SqlCommand cmd = new SqlCommand("spCheckProduct", conn);
cmd.CommandType = CommandType.StoredProcedure;

// 对参数的类型进行检查
SqlParameter parm = 
cmd.Parameters.Add("@ProductCode", 
SqlDbType.VarChar,12);
parm.Value = productCode;
// 定义输出参数
SqlParameter retparm = cmd.Parameters.Add("@quantity", SqlDbType.Int);
retparm.Direction = ParameterDirection.Output;
conn.Open();
cmd.ExecuteNonQuery();
quantity = (int)retparm.Value;
    }
  }
catch (SqlException sqlex)
  {
// (5) 记录完整的异常详细信息。一般(安全的)错误消息
//     基于 SQL 错误代码返回调用方
//     为清楚起见,省略了日志和错误标识代码
throw new Exception("Error Processing Request");
  }
catch (Exception ex)
  {
// 记录完整的异常详细信息
throw new Exception("Error Processing Request");
  }
return quantity;
}

// (6) 将加密的数据库连接字符串存保留在注册表中
private static string GetConnectionString()
{
// 从注册表中检索密码文本;进程帐户必须
// 由注册表项的 ACL 授予“读取”访问权限
string encryptedString = (string)Registry.LocalMachine.OpenSubKey(
@"Software\OrderProcessing\")
.GetValue("ConnectionString");
// 使用托管的 DPAPI helper 库对该字符串进行解密
DataProtector dp = new DataProtector(DataProtector.Store.USE_MACHINE_STORE);
byte[] dataToDecrypt = Convert.FromBase64String(encryptedString);
return Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));
}

上面的代码显示出下列安全特征(由注释行中的数字进行标识)。

1.数据访问代码放在 try/catch 块中。这是防止在出现异常时将系统级信息返回到调用方所必需的。调用 ASP.NET Web 应用程序或 Web 服务会处理异常,并向客户端返回适当的一般错误消息,但是数据访问代码不依赖这些消息。

2.使用正则表达式验证输入。检查所提供的产品 ID,以便验证它只包含 A–Z 和 0–9 范围内的字符,而且不超过 12 个字符。这是旨在防止 SQL 注入攻击的一组对策中的第一个。

3.SqlConnection 对象是在 Microsoft Visual C#® using 语句的内部创建的。这可确保无论是否发生了异常,都断开方法内部的连接。这会降低拒绝服务攻击的威胁,该威胁尝试使用到数据库的所有可用连接。通过使用 finally 块可以实现类似的功能。

4.参数化存储过程用于数据访问。这是防止 SQL 注入的另一个对策。

5.不向客户端返回详细的错误信息。记录异常详细信息,以便帮助诊断问题。

6.加密的数据库连接字符串存储在注册表中。存储数据库连接字符串最安全的方法之一是,使用 DPAPI 加密该字符串,并将加密的密码文本存储在具有受限 ACL 的受保护的注册表项中。(例如,使用“管理员:完全控制”和“ASP.NET 或企业服务进程帐户:读取”,具体情况取决于由哪个进程托管该组件。)

注意 该代码显示了如何从注册表检索连接字符串,然后使用托管的 DPAPI helper 库对其进行解密。此库在“Microsoft patterns & practices Volume I, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的 How To: Create a DPAPI Library (英文)中提供。

posted @ 2010-08-03 18:14  远大 光明  阅读(245)  评论(0编辑  收藏  举报