Win32: Как проверить учетные данные в Active Directory?

Его спросили , и ответил для .NET , но теперь пришло время получить ответ для собственного кода Win32:

Как мне проверить Имя пользователя и пароль Windows?

Я задавал этот вопрос раньше для управляемого кода . Теперь пришло время для собственного решения.


Необходимо указать на подводные камни с помощью некоторых из наиболее часто предлагаемых решений:

Недействительный метод 1. Запросить Active Directory с олицетворением

Многие люди предлагают запрашивает что-то в Active Directory . Если генерируется исключение, значит, вы знаете, что учетные данные недействительны - как предлагается в этот вопрос о переполнении стека .

Однако есть некоторые серьезные недостатки этого подхода :

  • Вы не только аутентифицируете учетную запись домена, но также выполняете неявную проверку авторизации. То есть вы читаете свойства из AD с помощью токена олицетворения. Что, если действующая учетная запись не имеет прав на чтение из AD? По умолчанию все пользователи имеют доступ для чтения, но политики домена могут быть настроены на отключение разрешений доступа для ограниченных учетных записей (и / или групп).

  • Привязка к AD имеет серьезные накладные расходы, кэш схемы AD должен быть загружен на клиенте (Кэш ADSI в провайдере ADSI, используемом DirectoryServices). Это потребляет ресурсы и сеть, и сервер AD, и это слишком дорого для такой простой операции, как аутентификация учетной записи пользователя.

  • Вы полагаетесь на сбой исключения в неисключительном случае и предполагаете, что это означает недопустимое имя пользователя и пароль. Другие проблемы (например, сбой сети, сбой подключения к AD, ошибка выделения памяти и т. Д.) Затем неверно интерпретируются как сбой аутентификации.

Использование класса DirectoryEntry в .NET является примером неправильного способ проверки учетных данных:

Недействительный метод 1a - .NET

DirectoryEntry entry = new DirectoryEntry("persuis", "iboyd", "Tr0ub4dor&3");
object nativeObject = entry.NativeObject;

Недействительный метод 1b - .NET # 2

public static Boolean CheckADUserCredentials(String accountName, String password, String domain)
{
    Boolean result;

    using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain, accountName, password))
    {
        using (DirectorySearcher searcher = new DirectorySearcher(entry))
        {
            String filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
            searcher.Filter = filter;
            try
            {
                SearchResult adsSearchResult = searcher.FindOne();
                result = true;
            }
            catch (DirectoryServicesCOMException ex)
            {
                const int SEC_E_LOGON_DENIED = -2146893044; //0x8009030C;
                if (ex.ExtendedError == SEC_E_LOGON_DENIED)
                {
                    // Failed to authenticate. 
                    result = false;
                }
                else
                {
                    throw;
                }
            }
        }
    }

А также запрос Active Directory через соединение ADO:

Недействительный метод 1c - Собственный запрос

connectionString = "Provider=ADsDSOObject;
       User ID=iboyd;Password=Tr0ub4dor&3;
       Encrypt Password=True;Mode=Read;
       Bind Flags=0;ADSI Flag=-2147483648';"

SELECT userAccountControl 
FROM 'LDAP://persuis/DC=stackoverflow,DC=com'
WHERE objectClass='user' and sAMAccountName = 'iboyd'

Они оба не работают, даже если ваши учетные данные действительны , но у вас нет разрешения на просмотр записи в каталоге:

enter image description here

Недействительный метод 2. LogonUser Win32 API

Другие предложили использовать LogonUser () API-функция. Звучит неплохо, но, к сожалению, вызывающему пользователю иногда требуется разрешение, обычно предоставляемое только самой операционной системе:

Процесс, вызывающий LogonUser, требует привилегия SE_TCB_NAME. Если вызывающий процесс не имеет этого привилегия, LogonUser не работает и GetLastError возвращает ERROR_PRIVILEGE_NOT_HELD.

В некоторых случаях, процесс, который вызывает LogonUser также должен иметь SE_CHANGE_NOTIFY_NAME привилегия включено; в противном случае LogonUser не работает и GetLastError возвращает ERROR_ACCESS_DENIED. Эта привилегия не требуется для локальной системы учетная запись или учетные записи, которые являются участниками группы администраторов. От по умолчанию SE_CHANGE_NOTIFY_NAME - включен для всех пользователей, но некоторые администраторы могут отключить его для

Распространение привилегии « Действовать как часть операционной системы » - это не то, чем вы хотите заниматься волей-неволей - как Microsoft указывает в статье базы знаний ]:

... вызывающий процесс LogonUser должен иметь SE_TCB_NAME привилегия (в Диспетчере пользователей это " Закон как часть Операционного Система "справа). SE_TCB_NAME привилегия очень сильна и не следует предоставлять произвольным пользователям только для того, чтобы они могли запустить приложение , которому необходимо проверить учетные данные.

Кроме того, вызов LogonUser () завершится ошибкой, если указан пустой пароль.


Действительный метод .NET 3.5 - PrincipalContext

Существует метод проверки, доступный только в .NET 3.5 и новее, что позволяет пользователю выполнять аутентификацию без выполнения проверки авторизации:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "stackoverflow.com"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("iboyd", "Tr0ub4dor&3")
}

К сожалению, этот код доступен только в .NET 3.5 и более поздних версиях.

Пришло время найти эквивалент native .

16
задан Community 23 May 2017 в 12:24
поделиться