ADO.NET 连接字符串
3.5 连接字符串前面的代码段中已经用到通过类的构造函数来设置SqlConnection对象的ConnectionString属性。但是,我们还必须讨论连接字符串是什么。 3.5.1 连接字符串是什么在前面的代码段中,为新的SqlConnection对象提供了一个连接字符串。连接字符串由一系列用分号隔开的名称-值对组成。 strConn = "Setting1=Value1;Setting2=Value2;..." 设置和值取决于希望连接的数据源,以及为连接到数据源而采用的技术。 SQL Client .NET数据提供程序在连接到数据库时极其灵活,它提供了多种用以生成连接字符串的方式。可以使用当前关键字,例如“Data Source”和“Initial Catalog”,也可以使用旧术语,如“Server”和“Database”。下面是连接字符串的两个简单例子,用于连接到SQL Server数据库。 1. 连接到SQL Server的本地默认实例可以使用多种特殊值来表示正在连接到本地计算机——“(local)”、“localhost”或“.”(本人喜欢使用这种方式)。只需要在连接字符串的“Data Source”中指定希望访问的计算机名称,如下所示: Data Source=.;
2. 连接到已命名实例在特定计算机上可以安装SQL Server的多个实例。前面的示例访问默认实例。如果希望访问SQL Server的一个已命名实例,向Data Source的值中添加一个反斜杠(\),然后添加SQL Server实例的名称。以下代码访问本地计算机上的SQLExpress已命名实例。 Data Source=.\SQLExpress;
Visual C# string strConn; strConn = "Data Source=.\\SQLExpress;"; //或 strConn = @"Data Source=.\SQLExpress;"; Console.WriteLine(strConn); 无论采用哪种方式,都将看到在控制台窗口中输出了单个反斜杠。本书在Visual C#代码段中的连接字符串中使用@符号,以便读者能够专注于字符串的内容,而不用关注转义序列。没有指示特定语言的代码段将显示所产生的字符串,所以Visual C#开发人员一定要记住:需要对字符串进行适当修改,以符合其代码规范。 3. 指定Initial CatalogSQL Server的任意实例可安装多个数据库。在连接到SQL Server的一个实例时,可以通过Initial Catalog关键字指定希望访问的数据库。如果希望访问SQL Server的本地默认实例上的Northwind数据库,可以使用以下代码: Data Source=.\SQLExpress;Initial Catalog=Northwind;
4. 使用特定用户名和密码进行连接许多数据库允许通过在连接字符串中提供用户名和密码来登录到数据存储区。可以通过使用User ID和Password连接字符串关键字为SqlConnection使用这些选项。下面是这种连接字符串的一个示例。 Data Source=.\SQLExpress;Initial Catalog=Northwind; User ID=MyUserID;Password=MyPassword;
5. 使用集成安全性连接另一种连接选项是使SQL Server利用用户的Windows凭据验证用户,而不是在连接字符串中指定用户名称和密码。下面是在连接字符串中使用集成安全性的一个示例。 Data Source=.\SQLExpress;Initial Catalog=Northwind; Integrated Security=True;
3.5.2 连接字符串生成器简介在设计时或运行时生成连接字符串会复杂一些。有可能难以记住希望使用的连接字符串选项名称;也有可能难以确定如何分隔其取值;也有可能正在寻求帮助,以确保从用户接收的输入不会改变连接字符串的目的。ADO.NET 2.0引入了连接字符串生成器类,以帮助开发人员解决在生成连接字符串时遇到的这些问题。 1. 使用连接字符串生成器连接字符串生成器的使用非常简单。可以通过这个类的索引器来设置和检查值。在提供了希望使用的值之后,就可以使用生成器的ConnectionString属性来访问所得到的连接字符串。 这里给出一个例子,在此例中希望基于各种连接字符串选项生成一个连接字符串,并对其进行修改,以使用SqlConnectionStringBuilder(它是SqlConnection对象的一个连接字符串生成器类)。我们只是通过索引器设置不同关键字的值。在生成需要的连接字符串之后,访问ConnectionString属性,并将结果传递给SqlConnection对象,如以下代码段所示: Visual Basic Dim bldr As New SqlConnectionStringBuilder() bldr("Data Source") = ".\SQLExpress" bldr("Initial Catalog") = "Northwind" bldr("Integrated Security") = True '将所生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString) '以SqlConnection使用所生成的连接字符串 Dim cn As New SqlConnection(bldr.ConnectionString) cn.Open() Visual C# SqlConnectionStringBuilder bldr = new SqlConnectionStringBuilder(); bldr["Data Source"] = @".\SQLExpress"; bldr["Initial Catalog"] = "Northwind"; bldr["Integrated Security"] = true; //将所生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString); //以SqlConnection使用所生成的连接字符串 SqlConnection cn = new SqlConnection(bldr.ConnectionString); cn.Open(); 将会看到输出到控制台窗口的连接字符串如下所示: Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True 当然,我们已经知道了如何生成这一连接字符串,但还没有真正对这些代码进行改进。 2. 连接字符串和IntelliSense我得承认,我在记忆一些连接字符串关键字和值时遇到了一些麻烦。我经常使用旧的开放式数据库连接(ODBC)语法来编写基本连接字符串,并将其传递给SqlConnection,或者添加Driver={SQL Server}或Provider=SQLOLEDB,并将该结果分别传递给OdbcConnection或OleDbConnection。对我来说,Trusted_Connection=Yes比Integrated Security=True的意思更明确,所以我从来没记住一些新选项的关键字。显然,我并不是唯一一个在记忆连接字符串选项时遇到麻烦的人。事实上,有一个网站专门介绍连接字符串选项。 连接字符串生成器通过将许多常用选项作为属性公开,简化了生成连接字符串的过程。ADO.NET 2.0中可供使用的连接字符串生成器类包括许多强类型属性,其对应于许多可用连接字符串选项。在前面的一个示例中,通过默认的索引器设置了Data Source,Initial Catalog和Integrated Security连接字符串关键字的值。可以通过访问SqlConnectionStringBuilder类的DataSource,InitialCatalog和IntegratedSecurity属性来重新编写此代码。下面是同一示例,只是这一次使用了SqlConnectionStringBuilder的属性。 Visual Basic Dim bldr As New SqlConnectionStringBuilder() bldr.DataSource = ".\SQLExpress" bldr.InitialCatalog = "Northwind" bldr.IntegratedSecurity = True '将所生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString) '以SqlConnection使用所生成的连接字符串 Dim cn As New SqlConnection(bldr.ConnectionString) cn.Open() Visual C# SqlConnectionStringBuilder bldr = new SqlConnectionStringBuilder(); bldr.DataSource = @".\SQLExpress"; bldr.InitialCatalog = "Northwind"; bldr.IntegratedSecurity = true; //将所生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString); //以SqlConnection使用所生成的连接字符串 SqlConnection cn = new SqlConnection(bldr.ConnectionString); cn.Open(); 此代码仍然生成相同的连接字符串,但此代码的编写更容易一些。另外,如果在提供连接字符串关键字时产生了输入错误,将会产生一个编译时错误。如果正在Microsoft Visual Studio中编写代码,并且难以记住连接字符串选项,那么可以很容易地通过IntelliSense下拉菜单使用SqlConnectionStringBuilder中的可用选项。使用这些下拉菜单还可以减少输入字符,防止出现一些输入错误,在其他情况下,需要到测试代码时才能发现这些错误。 3. 处理复杂的连接字符串选项值使用连接字符串生成器的一个间接好处是:不必再记忆如何分析、转义或分隔连接字符串中的值。假定需要提供一个包括空格的连接字符串值,是否需要在关键字两侧加引号呢?用大括号?一个也不用?还是用两个? 如果以手动方式生成连接字符串,那么这些问题不存在简单的答案。事实上,其答案取决于所使用的.NET数据提供程序。过去使用过ODBC的开发人员可能还会记得:包含空间的ODBC驱动程序名称必须用大括号分隔:"Driver={SQL Server}"。利用连接字符串生成器来代替手动方式生成连接字符串,就不再为这些问题而费心。 如果正在使用连接字符串生成器,可以将逻辑交给生成器处理。例如,可能希望使用连接字符串选项以连接到服务器,并在过程中附加一个数据库文件。希望使用的文件可能在路径中包含空格或其他字符。开发人员不必担心是否或如何分隔连接字符串中的文件名称,只需要使用SqlConnectionStringBuilder类来生成连接字符串,如下所示: Visual Basic Dim bldr As New SqlConnectionStringBuilder() bldr.DataSource = ".\SQLExpress" bldr.IntegratedSecurity = True bldr.AttachDBFilename = "C:\My Complex Path\AttachMe.mdf" '将生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString) '以SqlConnection使用所生成的连接字符串 Dim cn As New SqlConnection(bldr.ConnectionString) cn.Open() Visual C# SqlConnectionStringBuilder bldr = new SqlConnectionStringBuilder(); bldr.DataSource = @".\SQLExpress"; bldr.IntegratedSecurity = true; bldr.AttachDBFilename = @"C:\My Complex Path\AttachMe.mdf"; //将生成的连接字符串输出到控制台窗口 Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString); //以SqlConnection使用所生成的连接字符串 SqlConnection cn = new SqlConnection(bldr.ConnectionString); cn.Open(); 读者可能出于某种原因而希望了解如何正确分隔连接字符串中的文件名称,那么该代码段生成以下连接字符串: Data Source=.\SQLExpress;AttachDbFilename="C:\My Complex Path\AttachMe.mdf"; Integrated Security=True 4. 恶意连接字符串输入,也称为“连接字符串注入”在编写安全代码时,最重要的规则之一就是“绝对不要盲目相信用户输入”。参数化查询(这一主题将在第4章中详细讨论)非常有用,其原因有很多,但重要原因之一是它可以防止SQL注入。可以构建一个参数化查询,并为参数指定由用户提供的取值,而不必担心用户输入是否会改变查询的结构。在生成连接字符串时也存在类似问题。 您可能决定对用户进行提示,使其输入凭据——提供一个文本框以允许用户输入用户名和密码,然后利用以下代码,基于连接字符串中的输入来构造连接字符串。 Visual Basic Dim strConn As String strConn = "Data Source=.\SQLExpress;Initial Catalog=Northwind;" & _ "User ID=" & txtUserID.Text & ";" & _ "Password=" & txtPassword.Text & ";" Console.WriteLine("Resulting connection string: {0}", strConn) Visual C# string strConn; strConn = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" + "User ID=" + txtUserID.Text + ";" + "Password=" + txtPassword.Text + ";" Console.WriteLine("Resulting connection string: {0}", strConn); 乍看起来,这似乎是安全而符合逻辑的。现在假定有一位恶意用户,他希望修改此连接字符串。图3.1说明了一种方式,这种恶意用户可以采用这种方式来修改要访问哪个服务器。由于没有更好的术语,我们将这种方法称为“连接字符串注入”。
图3.1 恶意用户尝试通过用户输入改变应用程序访问的服务器 利用以上代码段和如图3.1所示的输入,所得到的连接字符串如下所示: Data Source=.\SQLExpress;Initial Catalog=Northwind;User ID=MyUserID; Data Source=EvilServerName;Password=MyPassword; 可以看到,Data Source被指明两次。在使用所得到的连接字符串时,会选用哪个值呢?是第一个(本地计算机),还是第二个(“evil”服务器)?是否存在手动检查用户输入的方式,以检查诸如此类的可能的连接字符串注入?这些答案对于其他连接字符串关键字或其他.NET数据提供程序是否正确呢?开发人员能够做些什么呢? 幸运的是,连接字符串生成器可以帮助开发人员处理来自恶意或恶作剧用户的输入。 5. 用连接字符串生成器防止连接字符串注入利用SqlConnectionStringBuilder重新编写前面的代码段,得到以下代码: Visual Basic Dim bldr As New SqlConnectionStringBuilder() bldr.DataSource = ".\SQLExpress" bldr.InitialCatalog = "Northwind" bldr.UserID = txtUserID.Text bldr.Password = txtPassword.Text Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString) Visual C# SqlConnectionStringBuilder bldr = new SqlConnectionStringBuilder(); bldr.DataSource = @".\SQLExpress"; bldr.InitialCatalog = "Northwind"; bldr.UserID = txtUserID.Text; bldr.Password = txtPassword.Text; Console.WriteLine("Resulting connection string: {0}", bldr.ConnectionString); 通过利用具有如图3.1所示恶意输入的SqlConnectionStringBuilder(对于用户名使用"MyUserID;Data Source=EvilServerName",对于密码使用MyPassword),将会生成以下连接字符串。 Data Source=.\SQLExpress;Initial Catalog=Northwind; User ID="MyUserID;Data Source=EvilServerName";Password=MyPassword 注意,User ID关键字的值被分隔。这会导致ADO.NET尝试以“MyUserID;Data Source=EvilServerName”为User ID来登录到SQL Server数据库。 6. 识别关键字的别名因为SqlConnectionStringBuilder是专门为SQL Server设计的,所以它能识别旧式关键字别名。可以向SqlConnectionStringBuilder提供当前或旧式关键字,ConnectionString属性将会返回相同值。 Visual Basic Dim bldr As SqlConnectionStringBuilder bldr = New SqlConnectionStringBuilder("Data Source=.\SQLExpress") Console.WriteLine(bldr.ConnectionString) bldr = New SqlConnectionStringBuilder("Server=.\SQLExpress") Console.WriteLine(bldr.ConnectionString) Visual C# SqlConnectionStringBuilder bldr; bldr = new SqlConnectionStringBuilder(@"Data Source=.\SQLExpress"); Console.WriteLine(bldr.ConnectionString); bldr = new SqlConnectionStringBuilder(@"Server=.\SQLExpress"); Console.WriteLine(bldr.ConnectionString); 无论执行以上代码段中的哪一段,ConnectionString属性将会返回以下值: Data Source=.\SQLExpress 7. 用连接字符串生成器生成连接字符串对话框诸如OLE DB和ODBC等先前技术都包含了用户界面组件,使开发人员能够提示用户输入连接信息——例如,允许用户从下拉列表中选择ODBC驱动程序或OLE DB提供程序。在大量新闻组和论坛中,都有开发人员询问如何用ADO.NET实现类似功能。简单的回答是:不存在简单的控件或对话框来生成ADO.NET连接字符串。较详细的回答是:可以相当容易地生成自己的对话框来显示连接字符串选项。 如果已经使用过Visual Studio 2005,可能已经注意到存在一些新对话框,用于连接到【服务器资源管理器】中的数据存储区。还有一些不同的对话框,用于SQL Server、Microsoft Access和Oracle数据库以及ODBC数据源。这些对话框中的每一个都是专门为所选择的数据库或技术所设计的。但是,每个对话框还包含一个标记为【高级】的按钮。单击这一按钮将显示另一个对话框,其中列出了用于该数据库或技术的高级选项。图3.2显示了用于SQL Client连接字符串的【高级属性】对话框。
图3.2 用于SQL Client连接字符串的【高级属性】对话框 如果希望对于不同.NET数据提供程序使用特定的对话框,就必须自己对其进行处理。但是,如果希望创建一个列出所有连接字符串选项的一般用户界面,可以利用PropertyGrid控件非常容易地实现这一功能,此控件是.NET Framework的一部分。只需要将PropertyGrid控件的SelectedObject属性设置为SqlConnectionStringBuilder,就可以产生如图3.3所示的对话框。剩下的任务就是捕获PropertyGrid控件的PropertyValueChanged事件,以利用SqlConnectionString对象的ConnectionString属性的当前值更新文本框,正如图3.3所示。 图3.3中的用户界面与图3.2所示用户界面相类似,其原因在于Visual Studio在其【高级属性】对话框中正在使用PropertyGrid控件和连接字符串生成器。
图3.3 绑定到SqlConnectionStringBuilder的PropertyGrid控件 3.5.3 连接字符串安全性假定希望使用一个简单的连接字符串对话框(类似于前面讨论PropertyGrid和SqlConnectionStringBuilder时的对话框),但希望仅允许输入特定的连接字符串。例如,可能希望将连接字符串限制于一个特定的服务器和初始目录,同时允许用户ID和密码取任意值,但不再有附加属性。应当如何编写这种代码呢? 一种方法是以SqlConnectionStringBuilder作为开始,其DataSource和InitialCatalog属性被设定为需要的值。然后可以创建自己的对话框,允许用户输入用户ID和密码的取值。接下来,将这些值分配给SqlConnectionStringBuilder,以避免连接字符串注入。 在一个非常简单的例子中(比如前面描述的例子),这种解决方案可能就足够了。但如果选项更为复杂,那么应当怎么办呢?比如,用户可以连接到服务器A和服务器B,但连接到服务器A的连接要求集成安全性,而连接到服务器B的连接需要用户ID和密码。后勤(logistics)越复杂,用户界面和验证逻辑也会变得越复杂。验证连接字符串的一种更简单方法是使用ADO.NET的新连接字符串安全特性。 1. 利用SqlClientPermission类来限制数据访问SqlClientPermission类是从System.Security命名空间中的CodeAccessPermission类派生而来。可以利用这个类来限制对特定连接字符串的访问,既可以通过类的属性与方法以可编程方式进行限制,也可以通过代码访问安全性来进行限制。 下面的代码示例说明如何使用SqlClientPermission类将访问限制于特定服务器名称和初始目录,同时允许连接字符串中的UserID和Password取任意值。此代码段假定有一个假想的“myDialog”,其返回利用SqlConnectionStringBuilder和PropertyGrid生成的连接字符串,它还假定引用了System.Security.Permissions命名空间。 Visual Basic Dim perm As New SqlClientPermission(PermissionState.None) perm.Add("Data Source=.\SQLExpress;Initial Catalog=Northwind;", _ "User ID=;Password=;", _ KeyRestrictionBehavior.AllowOnly) perm.PermitOnly() '尝试使用来自假设的myDialog的连接字符串 Dim cn As New SqlConnection(myDialog.ConnectionString) cn.Open() ... Visual C# SqlClientPermission perm; perm = new SqlClientPermission(PermissionState.None); perm.Add(@"Data Source=.\SQLExpress;Initial Catalog=Northwind;", "User ID=;Password=;", KeyRestrictionBehavior.AllowOnly); perm.PermitOnly(); //尝试使用来自假设的myDialog的连接字符串 SqlConnection cn = new SqlConnection(); cn.ConnectionString = myDialog.ConnectionString; cn.Open(); ... 在此代码段开始时创建SqlClientPermission类的一个新实例。构造函数中的PermissionState.None的值在开始时为空白状态,不允许存在连接字符串。然后可以通过调用Add方法添加希望允许的连接字符串。每一个参数包含希望允许的连接字符串子集,列出了所有必需的关键字和值——在本例中,列出了所希望的服务器名称和初始目录。 传递这一安全检查的连接字符串必须拥有Data Source和Initial Catalog的指定值。第二个参数是用分号分隔的可选连接字符串属性,在本例中列出了User ID和Password关键字。连接字符串可以拥有这些关键字的任意值,并传递这一安全检查。省略这些关键字的连接字符串也将传递这一安全检查。对于与此模式(pattern)相匹配的连接字符串,由第三个参数(也是最后一个参数)来控制是授予还是拒绝其权限。只要调用PermitOnly方法,就会执行此安全检查。 如果所提供的连接字符串通过了这些检查,则SqlConnection将尝试连接数据库。如果所提供的连接字符串未通过这些检查,则SqlConnection将在尝试连接数据库之前引发SecurityException。 2. 合并权限SqlClientPermission不限于单个连接字符串。可以向SqlClientPermission中添加多个连接(和通配符)。除非ConnectionString属性至少通过这些检查中的一项,否则对SqlConnection.Open的调用将不会成功。 3. 连接字符串关键字的同义词读者可能会问“SqlClientPermission是否允许使用连接字符串关键字的同义词?”,简单的回答是“允许”。 例如,在使用SqlClientPermission类时应用了Data Source和Initial Catalog关键字。SQL Client .NET数据提供程序知道Server和DataBase分别是这些连接字符串属性的同义词。只要连接字符串的其余部分能够通过此检查,SqlClientPermission就会接受此连接字符串,允许(尝试)连接到SQL Server数据库。 为什么不以Unrestricted AccessIn理论作为开始呢?如果希望允许连接至任意SQL Server数据库,只是在本地计算机上所安装的SQL Server Express的Northwind数据库除外,那么可以利用以下代码在开始时设置无限制权限,并拒绝特定的连接字符串。
Visual Basic Dim perm As New SqlClientPermission(PermissionState.Unrestricted) perm.Add("Data Source=.\SQLExpress;Initial Catalog=Northwind;", _ "User ID=;Password=;", _ KeyRestrictionBehavior.PreventUsage) perm.Deny() Dim strConn As String strConn = "Data Source=.\SQLExpress;" & _ "Initial Catalog=Northwind;User ID=...;Password=...;" Dim cn As New SqlConnection(strConn) cn.Open() ... Visual C# SqlClientPermission perm; perm = new SqlClientPermission(PermissionState.Unrestricted); perm.Add(@"Data Source=.\SQLExpress;Initial Catalog=Northwind;", "User ID=;Password=;", KeyRestrictionBehavior.PreventUsage); perm.Deny(); string strConn; strConn = @"Data Source=.\SQLExpress;" + "Initial Catalog=Northwind;User ID=...;Password=...;"; SqlConnection cn = new SqlConnection(strConn); cn.Open(); ... 此代码将会和预期中的一样,引发SecurityException。问题解决了吗?没有完全解决。 如果希望在开始时授予无限制权限,然后尝试拒绝对某单个资源的访问,这种算法的实现通常是“说着容易做着难”。如果使用下面的连接字符串尝试同一代码,会发生什么情况呢? Data Source=(local)\SQLExpress;Initial Catalog=Northwind; User ID=MyUserID;Password=MyPassword; 遗憾的是,SqlClientPermission没有认识到.\SQLExpress和(local)\SQLExpress是指同一服务器。因此,它将通过SqlClientPermission的检查,SqlConnection将尝试使用这一连接字符串连接SQL Server,这并不是我们所希望看到的结果。SqlClientPermission还将允许利用服务器IP地址连接数据库。这并不是在否定SqlClientPermission。有许多方法可以创建连接到单一SQL Server数据库的连接字符串,而用于确定这些字符串等价所使用的逻辑也是盘根错节的。如果SqlClientPermission确实要尝试包括此逻辑,那将会在性能方面付出很大的代价,而且还非常有可能使SqlClientPermission不能成功地识别出所有等价连接字符串。 换句话说,应当在开始时不授予任何权限,然后授予特定字符串进行访问的权限,而不应当在开始时授予无限制权限,然后再尝试禁止对特定资源的访问。 4. 作用域权限假定有一个名为Function1的函数,它使用SqlClientPermission来限制对SQL Server数据库的访问。这些限制的有效期是多长时间呢?如果Function1调用Function1A,这些限制是否还有效呢?如果在调用Function1之后又调用Function2,又会发生什么情况呢?这些限制在Function2期间是否有效呢? 在生成这些限制的函数生存期内,这些限制都有效。在以上各例中,这些限制将在Function1和Function1A中有效,但在Function2中无效。 5. 通过过程属性实施连接字符串安全显然,可以向一个过程应用属性来实施连接字符串安全。下面是一个简单的例子。 Visual Basic <SqlClientPermission( _ SecurityAction.PermitOnly, _ ConnectionString = "Server=.\SQLExpress;Initial Catalog=Northwind;" & _ "Integrated Security=True;", _ KeyRestrictions = "", _ KeyRestrictionBehavior = KeyRestrictionBehavior.AllowOnly)> _ Private Sub MyProcedure ... End Sub Visual C# [SqlClientPermission( SecurityAction.PermitOnly, ConnectionString = @"Server=.\SQLExpress;Initial Catalog=Northwind;" + "Integrated Security=True;", KeyRestrictions = "", KeyRestrictionBehavior = KeyRestrictionBehavior.AllowOnly)] private void MyProcedure() { ... } |



浙公网安备 33010602011771号