开发者

How can I check a user/password combination on an ActiveDirectory without putting the password in a String?

开发者 https://www.devze.com 2023-02-22 06:16 出处:网络
I want to check User/Password combination on a Windows domain. Right now I do it with the following code:

I want to check User/Password combination on a Windows domain. Right now I do it with the following code:

bool Login(String username, String password) {
    var principalContext = new PrincipalContext(ContextType.Domain);
    principalContext.ValidateCredentials(username, password);
}

While it works, the thing that bugs me is that I have to put the password in a String in order to use that API; as I am using a SecureString to store the password everywhere else, I would really like to use some way of checking the username / password combination without having to pass the password as a 开发者_StackOverflowmanaged System.String.

What would be the best way of achieving that?


One approach you could try might be:

Impersonate the user by calling LoginUser using P/Invoke, passing the password as SecureString as described in MSDN.

Connect to ActiveDirectory with the impersonated user, without passing the username and password:

AuthenticationTypes authenticationTypes = AuthenticationTypes.Secure;

using (var entry = new DirectoryEntry("LDAP://example.com", "", "", authenticationTypes))
{
    ...
}

I haven't tried this but it seems to me it ought to work.


Use DsBindWithCred. Note that this function fails with Access Denied even when the credentials are technically valid, such as the account being locked out. You will have to use the LogonUser function if you want that level of detail, but each call will count as a login attempt.

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

精彩评论

暂无评论...
验证码 换一张
取 消