Как я могу проверить комбинацию пользователь / пароль в ActiveDirectory, не вводя пароль в строку? - PullRequest
4 голосов
/ 05 апреля 2011

Я хочу проверить комбинацию Пользователь / Пароль в домене Windows. Прямо сейчас я делаю это с помощью следующего кода:

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

Хотя это работает, меня беспокоит то, что мне нужно ввести пароль в String, чтобы использовать этот API; так как я использую SecureString для хранения пароля где-либо еще, я действительно хотел бы использовать какой-либо способ проверки комбинации имени пользователя и пароля без необходимости передавать пароль как управляемый System.String.

Каков наилучший способ достижения этого?

Ответы [ 2 ]

3 голосов
/ 01 октября 2012

Использовать 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);
}
3 голосов
/ 05 апреля 2011

Один из подходов, которые вы можете попробовать, может быть:

Выдайте себя за пользователя, вызвав LoginUser с помощью P / Invoke, передав пароль как SecureString , как описано в MSDN .

Подключитесь к ActiveDirectory от имени пользователя, не передавая имя пользователя и пароль:

AuthenticationTypes authenticationTypes = AuthenticationTypes.Secure;

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

Я не пробовал, но мне кажется, это должно сработать.

...