8

我正在创建一个 Xaml/C# 应用程序,我希望它弹出一个登录提示。

我想知道是否可以使用 CredUIPromptForWindowsCredentials。

  • 显示 Windows 安全对话框
  • 获取输入的用户名和密码
  • 执行自定义验证
  • 如果验证成功 -> 继续应用
  • 否则,如果验证失败 -> - 通知用户无效的用户名或密码

我已经查看了Windows 安全登录表单?http://www.pinvoke.net/default.aspx/credui/creduipromptforwindowscredentials.html?diff=y但他们没有解释如何处理验证。

我真的很想要一个小例子,如果用户输入用户名=“Bo”和密码=“123”然后成功,否则显示错误消息并允许用户重试。

该应用程序将安装在多台计算机上。

或者这根本不可能?

更新

灵感来自这个问题的答案Show Authentication dialog in C# for windows Vista/7

我已修改代码以按预期工作。

请不要,验证部分仅用于概念证明。

WindowsSecurityDialog.cs

 public class WindowsSecurityDialog
    {

       public string CaptionText { get; set; }
       public string MessageText { get; set; }

        [DllImport("ole32.dll")]
        public static extern void CoTaskMemFree(IntPtr ptr);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct CREDUI_INFO
        {
            public int cbSize;
            public IntPtr hwndParent;
            public string pszMessageText;
            public string pszCaptionText;
            public IntPtr hbmBanner;
        }


        [DllImport("credui.dll", CharSet = CharSet.Auto)]
        private static extern bool CredUnPackAuthenticationBuffer(int dwFlags,
                                                                   IntPtr pAuthBuffer,
                                                                   uint cbAuthBuffer,
                                                                   StringBuilder pszUserName,
                                                                   ref int pcchMaxUserName,
                                                                   StringBuilder pszDomainName,
                                                                   ref int pcchMaxDomainame,
                                                                   StringBuilder pszPassword,
                                                                   ref int pcchMaxPassword);

        [DllImport("credui.dll", CharSet = CharSet.Auto)]
        private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere,
                                                                     int authError,
                                                                     ref uint authPackage,
                                                                     IntPtr InAuthBuffer,
                                                                     uint InAuthBufferSize,
                                                                     out IntPtr refOutAuthBuffer,
                                                                     out uint refOutAuthBufferSize,
                                                                     ref bool fSave,
                                                                     int flags);



        public bool ValidateUser()
        {
            var credui = new CREDUI_INFO
                                     {
                                         pszCaptionText = CaptionText,
                                         pszMessageText = MessageText
                                     };
            credui.cbSize = Marshal.SizeOf(credui);
            uint authPackage = 0;
            IntPtr outCredBuffer;
            uint outCredSize;
            bool save = false;


            const int loginErrorCode = 1326;    //Login Failed
            var authError = 0;

            while (true)
            {




                var result = CredUIPromptForWindowsCredentials(ref credui,
                                                               authError,
                                                               ref authPackage,
                                                               IntPtr.Zero,
                                                               0,
                                                               out outCredBuffer,
                                                               out outCredSize,
                                                               ref save,
                                                               1 /* Generic */);

                var usernameBuf = new StringBuilder(100);
                var passwordBuf = new StringBuilder(100);
                var domainBuf = new StringBuilder(100);

                var maxUserName = 100;
                var maxDomain = 100;
                var maxPassword = 100;
                if (result == 0)
                {
                    if (CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName,
                                                       domainBuf, ref maxDomain, passwordBuf, ref maxPassword))
                    {
                        //TODO: ms documentation says we should call this but i can't get it to work
                        //SecureZeroMem(outCredBuffer, outCredSize);

                        //clear the memory allocated by CredUIPromptForWindowsCredentials 
                        CoTaskMemFree(outCredBuffer);
                        var networkCredential = new NetworkCredential()
                                                {
                                                    UserName = usernameBuf.ToString(),
                                                    Password = passwordBuf.ToString(),
                                                    Domain = domainBuf.ToString()
                                                };

                        //Dummy Code replace with true User Validation
                        if (networkCredential.UserName == "Bo" && networkCredential.Password == "1234")
                            return true;
                        else //login failed show dialog again with login error
                        {
                            authError = loginErrorCode;
                        }



                    }
                }
                else return false;


            }
        }
    }

应用程序.xaml.cs

protected override void OnStartup(StartupEventArgs e)
        {
            var windowsSecurityDialog = new WindowsSecurityDialog
                                            {
                                                CaptionText = "Enter your credentials",
                                                MessageText = "These credentials will be used to connect to YOUR APP NAME";
                                            };

            if (windowsSecurityDialog.ValidateUser())
                base.OnStartup(e);
        }
4

2 回答 2

4

您将在Ookii 对话框中找到使用 CredUIPromptForWindowsCredentials 的 WPF 和 WinForms 的完整实现。

于 2012-04-26T14:21:19.950 回答
3

当我开始认为这是可能的时,我有点害怕。

答案是肯定的和否定的。您可以获得网络域和用户名,但是(谢天谢地),您无法获得实际密码,只能获得密码的哈希值。

从PInvoke大量借用,这是一个示例 WPF 应用程序,它引入并输出用户名和密码。

代码

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Interop;

namespace LoginDialog
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Declare/initialize variables.
            bool save = false;
            int errorcode = 0;
            uint dialogReturn;
            uint authPackage = 0;
            IntPtr outCredBuffer;
            uint outCredSize;

            // Create the CREDUI_INFO struct.
            CREDUI_INFO credui = new CREDUI_INFO();
            credui.cbSize = Marshal.SizeOf(credui);
            credui.pszCaptionText = "Connect to your application";
            credui.pszMessageText = "Enter your credentials!";
            credui.hwndParent = new WindowInteropHelper(this).Handle;

            // Show the dialog.
            dialogReturn = CredUIPromptForWindowsCredentials(
                ref credui, 
                errorcode, 
                ref authPackage,
                (IntPtr)0,  // You can force that a specific username is shown in the dialog. Create it with 'CredPackAuthenticationBuffer()'. Then, the buffer goes here...
                0,          // ...and the size goes here. You also have to add CREDUIWIN_IN_CRED_ONLY to the flags (last argument).
                out outCredBuffer, 
                out outCredSize, 
                ref save, 
                0); // Use the PromptForWindowsCredentialsFlags Enum here. You can use multiple flags if you seperate them with | .

            if (dialogReturn == 1223) // Result of 1223 means the user canceled. Not sure if other errors are ever returned.
                textBox1.Text += ("User cancelled!");
            if (dialogReturn != 0) // Result of something other than 0 means...something, I'm sure. Either way, failed or canceled.
                return;

            var domain = new StringBuilder(100);
            var username = new StringBuilder(100);
            var password = new StringBuilder(100);
            int maxLength = 100; // Note that you can have different max lengths for each variable if you want.

            // Unpack the info from the buffer.
            CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, username, ref maxLength, domain, ref maxLength, password, ref maxLength);

            // Clear the memory allocated by CredUIPromptForWindowsCredentials.
            CoTaskMemFree(outCredBuffer);

            // Output info, escaping whitespace characters for the password.
            textBox1.Text += String.Format("Domain: {0}\n", domain);
            textBox1.Text += String.Format("Username: {0}\n", username);
            textBox1.Text += String.Format("Password (hashed): {0}\n", EscapeString(password.ToString()));
        }

        public static string EscapeString(string s)
        {
            // Formatted like this only for you, SO.
            return s
                .Replace("\a", "\\a")
                .Replace("\b", "\\b")
                .Replace("\f", "\\f")
                .Replace("\n", "\\n")
                .Replace("\r", "\\r")
                .Replace("\t", "\\t")
                .Replace("\v", "\\v");
        }

        #region DLLImports
        [DllImport("ole32.dll")]
        public static extern void CoTaskMemFree(IntPtr ptr);

        [DllImport("credui.dll", CharSet = CharSet.Unicode)]
        private static extern uint CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere, int authError, ref uint authPackage, IntPtr InAuthBuffer,
          uint InAuthBufferSize, out IntPtr refOutAuthBuffer, out uint refOutAuthBufferSize, ref bool fSave, PromptForWindowsCredentialsFlags flags);

        [DllImport("credui.dll", CharSet = CharSet.Unicode)]
        private static extern bool CredUnPackAuthenticationBuffer(int dwFlags, IntPtr pAuthBuffer, uint cbAuthBuffer, StringBuilder pszUserName, ref int pcchMaxUserName, StringBuilder pszDomainName, ref int pcchMaxDomainame, StringBuilder pszPassword, ref int pcchMaxPassword);
        #endregion

        #region Structs and Enums
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct CREDUI_INFO
        {
            public int cbSize;
            public IntPtr hwndParent;
            public string pszMessageText;
            public string pszCaptionText;
            public IntPtr hbmBanner;
        }

        private enum PromptForWindowsCredentialsFlags
        {
            /// <summary>
            /// The caller is requesting that the credential provider return the user name and password in plain text.
            /// This value cannot be combined with SECURE_PROMPT.
            /// </summary>
            CREDUIWIN_GENERIC = 0x1,
            /// <summary>
            /// The Save check box is displayed in the dialog box.
            /// </summary>
            CREDUIWIN_CHECKBOX = 0x2,
            /// <summary>
            /// Only credential providers that support the authentication package specified by the authPackage parameter should be enumerated.
            /// This value cannot be combined with CREDUIWIN_IN_CRED_ONLY.
            /// </summary>
            CREDUIWIN_AUTHPACKAGE_ONLY = 0x10,
            /// <summary>
            /// Only the credentials specified by the InAuthBuffer parameter for the authentication package specified by the authPackage parameter should be enumerated.
            /// If this flag is set, and the InAuthBuffer parameter is NULL, the function fails.
            /// This value cannot be combined with CREDUIWIN_AUTHPACKAGE_ONLY.
            /// </summary>
            CREDUIWIN_IN_CRED_ONLY = 0x20,
            /// <summary>
            /// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only. We recommend that external callers not set this flag.
            /// </summary>
            CREDUIWIN_ENUMERATE_ADMINS = 0x100,
            /// <summary>
            /// Only the incoming credentials for the authentication package specified by the authPackage parameter should be enumerated.
            /// </summary>
            CREDUIWIN_ENUMERATE_CURRENT_USER = 0x200,
            /// <summary>
            /// The credential dialog box should be displayed on the secure desktop. This value cannot be combined with CREDUIWIN_GENERIC.
            /// Windows Vista: This value is not supported until Windows Vista with SP1.
            /// </summary>
            CREDUIWIN_SECURE_PROMPT = 0x1000,
            /// <summary>
            /// The credential provider should align the credential BLOB pointed to by the refOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system.
            /// </summary>
            CREDUIWIN_PACK_32_WOW = 0x10000000,
        }
        #endregion
    }
}

测试

  1. 创建一个名为LoginDialog.
  2. 将 aTextBox放入MainWindow.xaml提供的名为textBox1.
  3. 替换文件中的代码MainWindow.xaml.cs
  4. 跑!

样本输出

给定密码“password”,这里是输出。

Domain: 
Username: EXAMPLE\fake
Password (hashed): @@D\a\b\f\n\rgAAAAAU-JPAAAAAAweFpM4nPlOUfKi83JLsl4jjh6nMX34yiH

注释

这适用于 WPF。它可以为具有正确权限的 Silverlight 工作。

我不知道为什么有人会为合法的自定义验证这样做。如果您想为您的应用程序创建登录名,我建议让客户端通过 SSL (https://) 连接到将检查使用 LINQ to SQL 提供的凭据的 ASP.NET 页面或 Web 服务。然后它可以向客户端发送通过/失败响应。

哦,为了上帝的爱和所有神圣的事物,你的用户密码加盐和散列

注意:如果您想使用此登录名来阻止用户在没有帐户/付款的情况下使用您的应用程序,上述所有方法都可以,但不足以阻止人们对应用程序进行逆向工程和破解(例如,欺骗它认为它已收到传递消息)。这种 DRM 完全是另一种游戏。

于 2012-04-26T07:18:48.323 回答