从非域计算机连接到域SQL Server 2005

时间:2010-05-12 20:17:15

标签: c# .net sql-server active-directory sql-authentication

我几天前问了一个问题(Access to SQL Server 2005 from a non-domain machine using Windows authentication),这个问题有一些有趣但不实用的建议。我想再次提出这个问题,但要弄清楚我的约束是什么:

我有一个Windows域,其中一台机器运行SQL Server 2005,并且配置为仅支持Windows身份验证。我想在同一网络上的计算机上运行C#客户端应用程序,但不在域上,并访问SQL Server 2005实例上的数据库。

我无法在任一台计算机上创建或修改操作系统或SQL Server用户,也无法对权限或模拟进行任何更改,也无法使用runas。

我知道我可以使用以下四个参数编写可以连接到SQL Server数据库的Perl和Java应用程序:服务器名称,数据库名称,用户名(域名为domain \ user)和密码。

在C#中,我尝试过各种各样的事情:

string connectionString = "Data Source=server;Initial Catalog=database;User Id=domain\user;Password=password";
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();

并尝试将集成安全性设置为true和false,但似乎没有任何效果。我想在C#中做什么根本不可能?

感谢任何帮助,马丁

7 个答案:

答案 0 :(得分:8)

我遇到了类似的问题,我在编写一个需要在一个域上的计算机上运行的工具,并使用可信连接在另一个域上使用SQL服务器进行身份验证。我在这个问题上找到的一切都说不能做到。相反,您必须加入域,使用SQL身份验证,参与一些名为Kerberos的讨论,或者让您的网络人员建立一个可信赖的关系,并列举一些替代方案。

事情是我知道我可以使用RUNAS以某种方式使用它,因为我用SSMS证明了它:

C:\WINDOWS\system32\runas.exe /netonly /savecred /user:megacorp\joe.bloggs "C:\Program Files\Microsoft SQL Server\90\Tools\Binn\VSShell\Common7\IDE\SqlWb.exe"

/ netonly标志允许我使用本地凭据执行exe并使用远程凭据访问网络,我认为,无论如何我从远程服务器获得了我期望的结果集。问题是runas命令使得调试应用程序变得非常困难,并且它闻起来并不好。

最终我在the code project上找到了这篇文章,其中讨论了操纵Active Directory的身份验证,以下是进行模拟的主要类:

    using System;
    using System.Runtime.InteropServices;  // DllImport
    using System.Security.Principal; // WindowsImpersonationContext

    namespace TestApp
    {
        class Impersonator
        {
            // group type enum
            enum SECURITY_IMPERSONATION_LEVEL : int
            {
                SecurityAnonymous = 0,
                SecurityIdentification = 1,
                SecurityImpersonation = 2,
                SecurityDelegation = 3
            }

            // obtains user token
            [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool LogonUser(string pszUsername, string pszDomain, string pszPassword,
                int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

            // closes open handes returned by LogonUser
            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            extern static bool CloseHandle(IntPtr handle);

            // creates duplicate token handle
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
                int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

            WindowsImpersonationContext newUser;

            /// 
            /// Attempts to impersonate a user.  If successful, returns 
            /// a WindowsImpersonationContext of the new users identity.
            /// 
            /// Username you want to impersonate
            /// Logon domain
            /// User's password to logon with
            /// 
            public Impersonator(string sUsername, string sDomain, string sPassword)
            {
                // initialize tokens
                IntPtr pExistingTokenHandle = new IntPtr(0);
                IntPtr pDuplicateTokenHandle = new IntPtr(0);
                pExistingTokenHandle = IntPtr.Zero;
                pDuplicateTokenHandle = IntPtr.Zero;

                // if domain name was blank, assume local machine
                if (sDomain == "")
                    sDomain = System.Environment.MachineName;

                try
                {
                    const int LOGON32_PROVIDER_DEFAULT = 0;

                    // create token
                    // const int LOGON32_LOGON_INTERACTIVE = 2;
                    const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
                    //const int SecurityImpersonation = 2;

                    // get handle to token
                    bool bImpersonated = LogonUser(sUsername, sDomain, sPassword,
                        LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref pExistingTokenHandle);

                    // did impersonation fail?
                    if (false == bImpersonated)
                    {
                        int nErrorCode = Marshal.GetLastWin32Error();

                        // show the reason why LogonUser failed
                        throw new ApplicationException("LogonUser() failed with error code: " + nErrorCode);
                    }

                    bool bRetVal = DuplicateToken(pExistingTokenHandle, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref pDuplicateTokenHandle);

                    // did DuplicateToken fail?
                    if (false == bRetVal)
                    {
                        int nErrorCode = Marshal.GetLastWin32Error();
                        CloseHandle(pExistingTokenHandle); // close existing handle

                        // show the reason why DuplicateToken failed
                        throw new ApplicationException("DuplicateToken() failed with error code: " + nErrorCode);
                    }
                    else
                    {
                        // create new identity using new primary token
                        WindowsIdentity newId = new WindowsIdentity(pDuplicateTokenHandle);
                        WindowsImpersonationContext impersonatedUser = newId.Impersonate();

                        newUser = impersonatedUser;
                    }
                }
                finally
                {
                    // close handle(s)
                    if (pExistingTokenHandle != IntPtr.Zero)
                        CloseHandle(pExistingTokenHandle);
                    if (pDuplicateTokenHandle != IntPtr.Zero)
                        CloseHandle(pDuplicateTokenHandle);
                }
            }

            public void Undo()
            {
                newUser.Undo();
            }
        }
    }

仅使用它:

Impersonator impersonator = new Impersonator("username", "domain", "password");

//Connect to and use SQL server

impersonator.Undo();

我在Undo方法中添加了,否则模仿对象往往会收集垃圾。我还修改了代码以使用LOGON32_LOGON_NEW_CREDENTIALS,但这是一个戳并运行以使其工作;我仍然需要完全理解它的作用,我感觉它和runas上的/ netonly标志一样。我还要稍微分解构造函数。

答案 1 :(得分:4)

在连接字符串中指定用户名和密码是无用的,因为这些意味着SQL身份验证,并且您已指定SQL Server仅接受Windows身份验证。

如果服务器不允许SQL身份验证,则连接的可能性是使用Windows身份验证,即。 IntegratedSecurity=true。这意味着您的客户端将进行身份验证,因为正在运行该流程的任何凭据(或当前正在模拟)。

要使Windows身份验证起作用,您必须选择以下选项之一:

  • 将非域名加入的计算机加入一个信任服务器域的域(可以是它自己的域!),然后将客户端进程作为域\用户凭据运行。
  • 使用NTLM镜像帐户:客户端和服务器上的一对本地用户具有相同的名称和密码。
  • 授予对SQL Server的ANONYMOUS访问权限。

如果您无法使客户端主机信任服务器域,也无法添加NTLM镜像帐户,并且SQL Server管理员足够理智,不能启用ANONYMOUS,那么您将无法连接。

答案 2 :(得分:2)

您必须configure SQL Server才能允许SQL Server身份验证,即使用用户名和密码进行身份验证。

您无法通过域用户名/密码进行身份验证,例如'服务器身份验证,即直接指定域用户名/密码。

我当然可能是错的,但我确信这不是C#或.NET的问题。如何在Perl或Java应用程序中登录SQL Server?

答案 3 :(得分:2)

正如您所说,Linux计算机上的JDBC或Perl都可以连接到使用Windows身份验证的SQL Server 和与当前登录用户不同的凭据。顺便提一句The same is true for Windows CE devices

我认为这不是C#的问题,而是SQL Server OLE DB驱动程序的问题。我猜上面提到的方法“假装是网络级别使用某些特定凭据的Windows机器”; SQL Server OLE DB驱动程序缺少的功能。因此,我的建议是寻找可以访问SQL Server数据库的替代(可能是商业?)OLE DB驱动程序。不过,我不确定是否存在这样的事情。

答案 4 :(得分:0)

我会给你一些我更熟悉的Java答案:我使用jTDS JDBC驱动程序和上面提到的四个参数。 Perl应用程序我不太了解,但是在Linux机器上运行,并且能够连接相同的参数。我无法更改SQL Server以支持SQL身份验证。

要回答Remus的建议,我不能做他建议的那三件事,但Java和Perl应用程序能够连接。还有其他想法吗?

谢谢,马丁

答案 5 :(得分:0)

prompt for credentials的选项吗?

答案 6 :(得分:0)

以下是使用jTDS JDBC驱动程序从非域计算机连接的示例代码:

的Class.forName( “net.sourceforge.jtds.jdbc.Driver”)的newInstance(); String url =“jdbc:jtds:sqlserver:// server / database; domain = domain”; conn = DriverManager.getConnection(url,“user”,“password”);