【问题标题】:Authenticate against a non-trusted domain from WPF application从 WPF 应用程序对不受信任的域进行身份验证
【发布时间】:2011-06-15 16:30:15
【问题描述】:

我有一个需要访问 SQL Server 2008 R2 数据库的 WPF 应用程序。使用活动目录保护数据库。该应用程序在连接到域并且用户成功登录的工作站上运行良好。

我现在需要能够在连接到不同域的工作站上运行相同的应用程序。不幸的是,这个新域由 SBS 控制,因此我无法在它们之间建立信任关系。

实际上,我(认为我)需要的是一种工具,允许用户输入已在托管数据库服务器的域上设置的凭据(用户名和密码),然后在连接到数据库时模拟该用户服务器。

我已经使用 LogonUser、LOGON32_PROVIDER_DEFAULT 和 LOGON32_LOGON_INTERACTIVE 尝试了 Microsoft 示例,但这似乎没有达到我想要的效果 - 而是抱怨我无法登录,因为我使用的工作站没有域帐户。

有人有其他建议吗?

【问题讨论】:

    标签: sql-server wpf security active-directory


    【解决方案1】:

    使用您当前的设置

    The database is secured using active directory
    

    我假设您的意思是您的 SQL 连接正在使用 Windows 身份验证。无论哪种方式,发生的事情可能比您意识到的要多。如果您使用“活动目录”进行身份验证,我敢打赌您依赖Kerberos 进行 Windows 身份验证(这不仅是您的身份验证类型,还包括您的凭据类型)。我链接的文章解释了 SQL Server 2005 的 Kerberos 和 NTLM 之间的区别,但对于 2008 R2 来说是相同的。

    不幸的是,如果域之间没有信任关系,那么无论您是否成功模拟了托管数据库的域中的用户,都将无法连接(正如您所见)。 您需要这种信任关系

    如果您确实设法在域之间建立信任关系,我已经发布了如何使用域组here 为 SQL Server 完成跨域 Windows 身份验证,这可能对您有用。

    替代设置

    如果您不想使用 SQL 身份验证(我不喜欢将凭据与我的应用程序打包在一起),我鼓励您将数据库操作分离到更加面向服务的架构中。因此,实际执行的 SQL 工作将进入 WCF 服务(与您的数据库托管在同一域中并模拟服务身份),您的应用程序将简单地请求该服务。然后,您可以使用 NTLM 来保护您的 Web 服务以仍然使用 Windows 身份验证。这样,您仍然可以验证请求用户是谁,并依靠您自己的基本安全结构(即简单表)来授权使用。

    如果这对您没有意义,或者您需要进一步澄清,请告诉我。

    【讨论】:

    • 我喜欢这种服务方式……这将是一种优雅的方式。 +1
    • 你是对的,我的意思是使用 Windows 身份验证进行保护。虽然我同意 WCF 方法更可取,但此时应用程序太远了,无法更改 - 我打算稍后使用 WCF,但直到我克服了传递数据量的一些基本问题先来回。
    • 如果您计划对数据服务层进行更改(希望您将代码分成多个层),我会更加强调切换到 WCF 实现,以便在您部署更改时,您的用户不必更新他们的应用程序。相反,您只需更新您的服务。此外,如果您在一层中确实有数据操作,那么将代码移植到 WCF 服务并简单地使用“服务引用”客户端(内置于 VS2010)引用该服务将是一个较小的工作量。
    • 由于您的安全限制,如果您按原样保留代码,您将不得不使用 SQL 身份验证并将凭据与您的应用程序打包在一起(不鼓励),或者您必须以某种方式建立如果您将 Windows 身份验证与 Kerberos 一起使用,则域之间的信任关系(甚至只是一种方式)。如果您使用 NTLM,您可能可以在没有信任关系的情况下(尽管我认为您不能)使用不同的连接协议(即不是 TCP/IP)。
    【解决方案2】:

    我确实有一个建议……但我认为你不会喜欢它。

    使用 SQL 身份验证而不是集成。

    我遇到了类似的问题,在解决之后 - 结果发现真的没有办法让它发生。 See here for my post on dba.stackexchange.com

    如果使用 SQL 身份验证不是一个选项,那么仍然“可能”是一种管道胶带/保释线/完全黑客的方式来完成它,但您可能必须将问题迁移到服务器管理员论坛之一而不是 StackOverflow - 因为它不再是一个编程问题。

    【讨论】:

    • 你是对的——我不喜欢它!我个人厌恶使用 SQL 身份验证,但我承认它有它的位置(只是在我写的任何东西中都没有)。
    • 我明白了……因为我们正在做的事情结果证明是“唯一”的方式。
    【解决方案3】:

    好吧,看来这毕竟是可以做到的。我找到了以下link,这让我意识到我的主要问题是对 LogonUser API 调用的 LOGON32_LOGON_INTERACTIVE 参数使用不正确(它应该是 LOGON32_LOGON_NEWCREDENTIALS)。

    因此,我现在可以使用以下代码连接到 SQL Server 上的数据库,该数据库受 Windows 身份验证保护,但位于与运行代码的工作站完全无关的域上...

    static void Main(string[] args) {

    SafeTokenHandle safeTokenHandle;
    
    try {
    
        string userName = @"*****", domainName = @"*****", password = @"*****";
        bool returnValue = NativeMethods.LogonUser(userName, domainName, password, 
            NativeMethods.LogonType.NewCredentials, NativeMethods.LogonProvider.Default, out safeTokenHandle);
    
        if (false == returnValue) {
            int ret = Marshal.GetLastWin32Error();
            Console.WriteLine("LogonUser failed with error code : {0}", ret);
            throw new Win32Exception(ret);
        }
    
        using (safeTokenHandle) {
    
            WindowsIdentity windowsIdentity = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
            using (WindowsImpersonationContext impersonationContext = windowsIdentity.Impersonate()) {
    
                using (DataTable table = new DataTable()) {
                    using (SqlDataAdapter adapter = new SqlDataAdapter()) {
                        using (adapter.SelectCommand = new SqlCommand(@"select * from dbo.MyTable")) {
                            adapter.SelectCommand.CommandType = CommandType.Text;
                            using (adapter.SelectCommand.Connection = new SqlConnection(@"Data Source=Server;Initial Catalog=Database;Integrated Security=Yes")) {
                                adapter.SelectCommand.Connection.Open();
                                adapter.Fill(table);
                            }
                        }
                    }
    
                    Console.WriteLine(string.Format(@"{0} Rows retrieved.", table.Rows.Count));
    
                }
    
            }
    
        }
    
    }
    catch (Exception ex) {
        Console.WriteLine("Exception occurred. " + ex.Message);
    }
    

    当然,它需要整理,我需要提示用户输入他们的凭据(他们无法说服我硬编码凭据),但原则上它可以工作(匿名化除外)。

    希望这对其他人有所帮助。

    哦,你还需要以下...

    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid {
    
        private SafeTokenHandle() : base(true) {
        }
    
        protected override bool ReleaseHandle() {
            return NativeMethods.CloseHandle(handle);
        }
    
    }
    
    [DllImport(@"kernel32.dll")]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseHandle(
        IntPtr handle);
    
    [DllImport(@"advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool LogonUser(
        String lpszUsername,
        String lpszDomain,
        String lpszPassword,
        LogonType dwLogonType,
        LogonProvider dwLogonProvider,
        out SafeTokenHandle phToken);
    
    public enum LogonType {
        Interactive = 2,
        Network = 3,
        Batch = 4,
        Service = 5,
        Unlock = 7,
        NetworkClearText = 8,
        NewCredentials = 9
    }
    
    public enum LogonProvider {
        Default = 0,
        WinNT35 = 1,
        WinNT40 = 2,
        WinNT50 = 3
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-30
      • 2023-03-05
      • 1970-01-01
      • 2018-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多