我正在创建一个Xaml / C#应用程序,我希望它能够通过登录提示进行弹出。
我想知道是否可以使用CredUIPromptForWindowsCredentials。
我已经查看了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);
}
答案 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
}
}
<强>测试强>
LoginDialog
的新WPF应用程序。TextBox
放入名为MainWindow.xaml
的{{1}}文件中。textBox1
文件中的代码。示例输出
给定密码“password”,这是输出。
MainWindow.xaml.cs
<强>评论强>
这适用于WPF。它适用于带有right permissions的Silverlight。
我不知道为什么有人会为合法的自定义验证做这件事。如果要为应用程序创建登录,我建议客户端通过SSL(https://)连接到ASP.NET页面或Web服务,该服务将检查使用LINQ to SQL提供的凭据。然后它可以向客户端发送通过/失败响应。
哦,为了上帝的爱和所有圣洁的事,salt and hash your users' passwords。
注意:如果您想要使用此登录信息来阻止用户在没有帐户/支付的情况下使用您的应用程序,则上述所有内容都有效,但不足以阻止人们进行逆向工程和破解应用程序(例如,欺骗它以为它收到了传递消息)。那种DRM是一个整体'其他球赛。