Windows安全自定义登录验证

时间:2012-04-20 07:33:42

标签: c# .net windows

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

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

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

我已经查看了Windows Security login form?http://www.pinvoke.net/default.aspx/credui/creduipromptforwindowscredentials.html?diff=y,但他们没有解释如何处理验证。

我真的很喜欢一个小例子,如果用户输入username =“Bo”和password =“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;


            }
        }
    }

App.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);
        }

2 个答案:

答案 0 :(得分:4)

您将在Ookii dialogs使用CredUIPromptForWindowsCredentials找到WPF和WinForms的完整实现。

答案 1 :(得分: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的新WPF应用程序。
  2. TextBox放入名为MainWindow.xaml的{​​{1}}文件中。
  3. 替换textBox1文件中的代码。
  4. 运行!
  5. 示例输出

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

    MainWindow.xaml.cs

    <强>评论

    这适用于WPF。它适用于带有right permissions的Silverlight。

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

    哦,为了上帝的爱和所有圣洁的事,salt and hash your users' passwords

    注意:如果您想要使用此登录信息来阻止用户在没有帐户/支付的情况下使用您的应用程序,则上述所有内容都有效,但不足以阻止人们进行逆向工程和破解应用程序(例如,欺骗它以为它收到了传递消息)。那种DRM是一个整体'其他球赛。