Windows安全性自定义loginvalidation

我正在创build一个Xaml / C#应用程序,我希望它popup一个login提示。

我想知道是否有可能使用CredUIPromptForWindowsCredentials。

  • 显示Windows安全对话框
  • 获取input的用户名和密码
  • 执行自定义validation
  • 如果validation成功 – >继续应用程序
  • 否则如果validation失败 – > – 用户input无效的用户名或密码

我已经看了Windows安全login窗体? 和http://www.pinvoke.net/default.aspx/credui/creduipromptforwindowscredentials.html?diff=y但他们不解释如何处理validation。

我真的很喜欢一个小例子,如果用户input用户名=“Bo”和密码=“123”,则显示错误信息并允许用户再次尝试。

该应用程序将被安装在多台电脑上。

或者这是不可能的?

更新

受此问题的答案在Windows Vista / 7的C#中显示身份validation对话框的启发

我修改了代码以按预期方式工作。

请注意,validation部分仅用于概念validation。

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

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

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

答案是肯定的,不是。 你可以得到一个网络域和用户名,但是(谢天谢地),你不能得到一个真正的密码,只有密码的散列。

从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放入提供名为textBox1MainWindow.xaml文件中。
  3. 替换MainWindow.xaml.cs文件中的代码。
  4. 跑!

示例输出

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

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

注释

这适用于WPF。 它可以为Silverlight提供正确的权限 。

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

噢,为了上帝的爱和圣洁的一切, 给你的用户密码加上密码 。

注意:如果你想使用这个登录来防止用户使用你的应用程序没有帐户/支付,上述所有,但不足以阻止人们反向工程和破解应用程序(例如,欺骗它认为它收到了通过消息)。 这种数字版权管理是一个完整的“无球游戏”。