我想检查Windows域上的用户/密码组合。现在我使用以下代码执行此操作:
bool Login(String username, String password) {
var principalContext = new PrincipalContext(ContextType.Domain);
principalContext.ValidateCredentials(username, password);
}
虽然它有效,但让我烦恼的是我必须将密码放在String
中才能使用该API;因为我使用SecureString
将密码存储在其他地方,我真的想用某种方式检查用户名/密码组合,而不必将密码作为托管System.String
传递。
实现这一目标的最佳方法是什么?
答案 0 :(得分:3)
您可以尝试的一种方法可能是:
通过使用P / Invoke调用LoginUser来模拟用户,将密码作为SecureString as described in MSDN传递。
使用模拟用户连接到ActiveDirectory,而不传递用户名和密码:
AuthenticationTypes authenticationTypes = AuthenticationTypes.Secure;
using (var entry = new DirectoryEntry("LDAP://example.com", "", "", authenticationTypes))
{
...
}
我没有尝试过,但在我看来它应该有用。
答案 1 :(得分:3)
使用DsBindWithCred。请注意,即使凭据在技术上有效(例如帐户被锁定),此功能也会因拒绝访问而失败。如果您想要详细程度,则必须使用LogonUser功能,但每次调用都将算作登录尝试。
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Text;
public class PInvoke
{
public static bool TestCreds(string usernamePossiblyWithDomain,
SecureString password,
string dnsDomainName)
{
string username, usernameDomain;
ParseUserName(usernamePossiblyWithDomain, out username, out usernameDomain);
IntPtr pPass = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
IntPtr hDS = IntPtr.Zero;
IntPtr authID = MakePassCreds(username, usernameDomain, pPass);
//if you're really paranoid, you can uncomment the next two lines
//to zero out the memory as soon as possible
//Marshal.ZeroFreeGlobalAllocUnicode(pPass);
//pPass = IntPtr.Zero;
try
{
int lastErr = DsBindWithCred(null, dnsDomainName, authID, ref hDS);
switch(lastErr)
{
case 0: return true; //ERROR_SUCCESS
case 5: return false; //ERROR_ACCESS_DENIED
default: throw new Win32Exception(lastErr);
}
}
finally
{
if(hDS != IntPtr.Zero) DsUnBind(ref hDS);
if(authID != IntPtr.Zero) DsFreePasswordCredentials(authID);
}
}
finally
{
if(pPass != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(pPass);
}
}
[DllImport("credui.dll", CharSet = CharSet.Unicode)]
protected static extern int CredUIParseUserName(string pszUserName,
StringBuilder pszUser, int ulUserMaxChars,
StringBuilder pszDomain, int ulDomainMaxChars);
public static void ParseUserName(string usernamePossiblyWithDomain,
out string username, out string domain)
{
int MaxUserChars = 256, maxDomainChars = 256;
StringBuilder sbUser = new StringBuilder(maxUserChars);
StringBuilder sbDomain = new StringBuilder(maxDomainChars);
int lastErr = CredUIParseUserName(usernamePossiblyWithDomain, sbUser,
maxUserChars - 1, sbDomain, maxDomainChars - 1);
if(lastErr != 0) throw new Win32Exception(lastErr);
username = sbUser.ToString();
domain = sbDomain.ToString();
}
[DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
protected static extern int DsMakePasswordCredentials(
string User, string Domain, IntPtr Password, ref IntPtr pAuthIdentity);
[DllImport("ntdsapi.dll")]
public static extern int DsFreePasswordCredentials(IntPtr AuthIdentity);
//caller is responsible for calling DsFreePasswordCredentials on the return val
public static IntPtr MakePassCreds(string username, string domain, IntPtr pPass)
{
IntPtr auth = IntPtr.Zero;
int lastErr = DsMakePasswordCredentials(username, domain, pPass, ref auth);
if(lastErr != 0) throw new Win32Exception(lastErr);
return auth;
}
[DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
protected static extern int DsBindWithCred(string DomainControllerName,
string DnsDomainName, IntPtr AuthIdentity, ref IntPtr phDS);
[DllImport("ntdsapi.dll")]
public static extern int DsUnBind(ref IntPtr phDS);
}