Проверить имя пользователя и пароль в Active Directory?

Как я могу проверить имя пользователя и пароль в Active Directory? Я просто хочу проверить правильность имени пользователя и пароля.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
548
0
503 638
14

Ответы 14

Вероятно, самый простой способ - это PInvoke LogonUser Win32 API. Например.

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

Ссылка MSDN здесь ...

http://msdn.microsoft.com/en-us/library/aa378184.aspx

Определенно хочу использовать тип входа

LOGON32_LOGON_NETWORK (3)

Это создает только легкий токен - идеально подходит для проверок AuthN. (другие типы могут использоваться для создания интерактивных сеансов и т. д.)

Как указывает @Alan, у LogonUser API есть много полезных свойств, помимо вызова System.DirectoryServices.

stephbu 01.12.2008 20:48

@cciotti: Нет, это неправильно. ЛУЧШИЙ способ правильно аутентифицировать кого-то - использовать LogonUserAPI как @stephbu write. Все остальные методы, описанные в этом посте, НЕ РАБОТАЮТ на 100%. Замечу, однако, я считаю, что вы должны быть присоединены к домену, чтобы вызвать LogonUser.

Alan 20.04.2009 22:28

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

stephbu 21.04.2009 06:15

API LogonUser требует, чтобы у пользователя был уровень Действовать как часть операционной системы; это не то, что получают пользователи, и не то, что вы хотите предоставлять каждому пользователю в организации. (msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx)

Ian Boyd 18.08.2011 17:52

Согласен Иэн, только LogonUser и SSPI могут отображать весь спектр возможных результатов проверки кредита. Выполнение SSPI на C#, как того требует OP, далеко не просто, поэтому рекомендуется LogonUser. Вы видели библиотеку, которая элегантно объединяет ее, не прибегая к неуправляемому коду. Я не смотрел ТБХ.

stephbu 24.08.2011 10:20

LogonUser требует только Действовать как часть операционной системы для Windows 2000 и ниже в соответствии с support.microsoft.com/kb/180548 ... Он выглядит чистым для Server 2003 и выше.

Chris J 08.09.2011 19:14

LogonUser не будет работать, если домен не является доменом, которому вы доверяете. Если вы хотите проверить учетные данные домена, вам нужно будет выполнить привязку к LDAP-серверу AD.

Ian Boyd 19.03.2015 17:14

Попробуйте этот код (ПРИМЕЧАНИЕ: Сообщается, что не работает на Windows Server 2000)

#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername, 
    String lpszDomain, String lpszPassword, int dwLogonType, 
    int dwLogonProvider, out int phToken);

[DllImport("Kernel32.dll")]
private static extern int GetLastError();

public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
   int token1, ret;
   int attmpts = 0;

   bool LoggedOn = false;

   while (!LoggedOn && attmpts < 2)
   {
      LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
      if (LoggedOn) return (true);
      else
      {
         switch (ret = GetLastError())
         {
            case (126): ; 
               if (attmpts++ > 2)
                  throw new LogonException(
                      "Specified module could not be found. error code: " + 
                      ret.ToString());
               break;

            case (1314): 
               throw new LogonException(
                  "Specified module could not be found. error code: " + 
                      ret.ToString());

            case (1326): 
               // edited out based on comment
               //  throw new LogonException(
               //   "Unknown user name or bad password.");
            return false;

            default: 
               throw new LogonException(
                  "Unexpected Logon Failure. Contact Administrator");
              }
          }
       }
   return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser

за исключением того, что вам нужно будет создать собственное исключение для «LogonException»

Не используйте обработку исключений для возврата информации из метода. «Неизвестное имя пользователя или неверный пароль» не является исключением, это стандартное поведение для LogonUser. Просто верните false.

Treb 14.11.2008 19:20

да ... это был порт из старой библиотеки VB6 ... написанной в 2003 году или около того ... (когда впервые появился .Net)

Charles Bretana 17.11.2008 18:18

При запуске в Windows 2000 этот код не будет работать (support.microsoft.com/kb/180548)

Ian Boyd 01.12.2008 17:58

Переосмысление этого. Ожидаемое поведение пользователя при входе в систему, его цель - войти в систему. Если ему не удается выполнить эту задачу, это ЯВЛЯЕТСЯ исключение. Фактически, метод должен возвращать void, а не логическое значение. Кроме того, если вы только что вернули логическое значение, у потребителя метода не будет возможности сообщить пользователю, в чем причина сбоя.

Charles Bretana 24.05.2018 15:34

Мы делаем это в нашем Интранете

Вы должны использовать System.DirectoryServices;

Вот внутренности кода

using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
    using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
    {
        //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
        adsSearcher.Filter = "(sAMAccountName = " + strAccountId + ")";

        try
        {
            SearchResult adsSearchResult = adsSearcher.FindOne();
            bSucceeded = true;

            strAuthenticatedBy = "Active Directory";
            strError = "User has been authenticated by Active Directory.";
        }
        catch (Exception ex)
        {
            // Failed to authenticate. Most likely it is caused by unknown user
            // id or bad strPassword.
            strError = ex.Message;
        }
        finally
        {
            adsEntry.Close();
        }
    }
}

Что вы вкладываете в «путь»? Имя домена? Имя сервера? Путь LDAP к домену? Путь LDAP к серверу?

Ian Boyd 01.12.2008 18:00

Ответ1: Нет, мы запускаем его как веб-службу, поэтому его можно вызывать из разных мест в основном веб-приложении. Ответ2: Путь содержит информацию LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com

DiningPhilanderer 01.12.2008 21:21

Казалось бы, это может позволить инъекцию LDAP. Возможно, вы захотите избежать или удалить скобки в strAccountId

Brain2000 09.10.2014 19:16

Означает ли это, что strPassword хранится в LDAP в виде обычного текста?

Matt Kocaj 17.07.2015 12:28

Никогда не должно быть необходимости явно вызывать Close() для переменной using.

Nyerguds 17.05.2016 12:28

Мне нужно проверить, а затем получить некоторую информацию о подтвержденном пользователе. этот код работает для меня. Спасибо

Mazaher Bazari 03.06.2019 10:48

используйте путь, например entry.Path = @ "LDAP: // OU = " "Пользователи компании" ", OU = Company, DC = boise, DC = esiconstruction, DC = com"; Путь идет справа налево, где крайняя правая организационная единица является верхним узлом.

Golden Lion 18.06.2020 19:28

очень простое решение с использованием DirectoryServices:

using System.DirectoryServices;

//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
    bool authenticated = false;

    try
    {
        DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
        object nativeObject = entry.NativeObject;
        authenticated = true;
    }
    catch (DirectoryServicesCOMException cex)
    {
        //not authenticated; reason why is in cex
    }
    catch (Exception ex)
    {
        //not authenticated due to some other exception [this is optional]
    }

    return authenticated;
}

доступ к NativeObject требуется для обнаружения неверного пользователя / пароля

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

Ian Boyd 18.08.2011 17:49

На самом деле я запрашиваю родные эквивалент PrincipleContext, который существует только в .NET 3.5. Но если вы используете .NET 3.5 или новее, вам следует использовать PrincipleContext.

Ian Boyd 18.08.2011 20:57

Полное решение .Net - использовать классы из пространства имен System.DirectoryServices. Они позволяют напрямую запрашивать сервер AD. Вот небольшой пример, который сделает это:

using (DirectoryEntry entry = new DirectoryEntry())
{
    entry.Username = "here goes the username you want to validate";
    entry.Password = "here goes the password";

    DirectorySearcher searcher = new DirectorySearcher(entry);

    searcher.Filter = "(objectclass=user)";

    try
    {
        searcher.FindOne();
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570)
        {
            // Login or password is incorrect
        }
    }
}

// FindOne() didn't throw, the credentials are correct

Этот код напрямую подключается к серверу AD, используя предоставленные учетные данные. Если учетные данные недействительны, searchcher.FindOne () выдаст исключение. Код ошибки соответствует ошибке COM «неверное имя пользователя / пароль».

Вам не нужно запускать код от имени пользователя AD. Фактически, я успешно использую его для запроса информации на сервере AD от клиента за пределами домена!

как насчет типов аутентификации? Я думаю, вы забыли это в своем коде выше. :-) по умолчанию для DirectoryEntry.AuthenticationType установлено значение «Защищено», верно? этот код не будет работать с незащищенными LDAP (возможно, Anonymous или None). я правильно с этим?

jerbersoft 12.11.2010 06:47

Обратной стороной запрос сервера AD является то, что у вас есть разрешение для запроса сервера AD. Ваши учетные данные могут быть действительными, но если у вас нет разрешения на запрос AD, вы получите сообщение об ошибке. Вот почему был создан так называемый Быстрая привязка; вы проверяете учетные данные, не разрешая пользователю что-либо делать.

Ian Boyd 19.03.2015 17:16

не позволит ли это кому-либо пройти, если по какой-либо другой причине будет выброшено исключение COMException до проверки учетных данных?

Stefan Paul Noack 05.01.2017 20:09

К сожалению, не существует "простого" способа проверить учетные данные пользователей в AD.

С каждым из представленных до сих пор методов вы можете получить ложноотрицательный результат: кредиты пользователя будут действительными, однако AD при определенных обстоятельствах вернет false:

  • Пользователь должен изменить пароль при следующем входе в систему.
  • Срок действия пароля пользователя истек.

ActiveDirectory не позволит вам использовать LDAP, чтобы определить, является ли пароль недействительным из-за того, что пользователь должен изменить пароль или его пароль истек.

Чтобы определить изменение пароля или срок действия пароля, вы можете вызвать Win32: LogonUser () и проверить код ошибки Windows на наличие следующих двух констант:

  • ERROR_PASSWORD_MUST_CHANGE = 1907.
  • ERROR_PASSWORD_EXPIRED = 1330

Могу я спросить, где у вас devinitions для Expired и Must_Change ... Нашел их нигде, кроме как здесь :)

mabstrei 19.11.2012 14:13

Из статьи MSDN: msdn.microsoft.com/en-us/library/windows/desktop/…

Alan 19.11.2012 20:38

Спасибо. Я пытался выяснить, почему моя проверка все время возвращала false. Это произошло потому, что пользователю нужно изменить свой пароль.

Deise Vicentin 27.11.2017 16:35

Если вы работаете с .NET 3.5 или новее, вы можете использовать пространство имен System.DirectoryServices.AccountManagement и легко проверить свои учетные данные:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

Это просто, это надежно, с вашей стороны это 100% управляемый код C# - чего еще вы желаете? :-)

Прочтите все об этом здесь:

Обновлять:

Как указано в этот другой вопрос SO (и его ответы), существует проблема с этим вызовом, который может возвращать True для старых паролей пользователя. Просто помните об этом поведении и не удивляйтесь, если это произойдет :-) (спасибо @MikeGledhill за указание на это!)

Я пробовал: PrincipalContext pc = new PrincipalContext (ContextType.Domain, "yourdomain"); Ваш пробег может отличаться.

Christian Payne 29.05.2009 09:03

Кристиан: Вы абсолютно правы. У меня не было под рукой сервера, чтобы проверить мои предположения - и они были неправильными. Вам нужно простое доменное имя, а не дескриптор домена LDAP. Спасибо что подметил это!

marc_s 29.05.2009 10:38

Это отлично сработало, только для меня мне пришлось добавить к имени пользователя имя домена, то есть pc.ValidateCredentials ("DOMAN \\ UserName", "Password");

Michael La Voie 29.10.2009 19:23

@myotherme: да, получите UserPrincipal, а затем используйте user.GetAuthorizationGroups(); для получения этого списка. Посмотрите отличную статью MSDN по этой теме: msdn.microsoft.com/en-us/magazine/cc135979.aspx

marc_s 12.04.2010 16:45

В моем домене мне пришлось указать pc.ValidateCredentials («myuser», «mypassword», ContextOptions.Negotiate), иначе я бы получил System.DirectoryServices.Protocols.DirectoryOperationExcepti‌ on: Сервер не может обрабатывать запросы каталога.

Alex Peck 29.06.2011 18:14

Если срок действия пароля истек или учетные записи отключены, ValidateCredentials вернет false. К сожалению, он не сообщает вам, что Почему вернул false (что очень жаль, поскольку это означает, что я не могу сделать что-то разумное, например, перенаправить пользователя для изменения его пароля).

Chris J 08.09.2011 19:10

Также будьте осторожны с учетной записью «Гость» - если учетная запись гостя на уровне домена включена, ValidateCredentials вернет true, если вы предоставите ей пользователя несуществующий. В результате вы можете вызвать в UserPrinciple.FindByIdentity, чтобы узнать, существует ли переданный идентификатор пользователя первым.

Chris J 08.09.2011 19:17

Мы сделали это в нашем домене разработки / тестирования (тот, который случайным образом запускается с ошибками для тестирования различных сценариев). Если бы не это, его бы не поймали, оставив зияющую дыру в безопасности. Проблема в том, что пока у нас есть контроль над нашим собственным доменом, у нас нет никакого контроля над доменом нашего клиента, поэтому я бы предпочел иметь пуленепробиваемый код, чем делать предположения :-) Жалко, что это поведение не было задокументировано в MSDN как потребовался возраст, чтобы выследить ...

Chris J 09.09.2011 12:48

@AlexPeck: причина, по которой вам пришлось это сделать (как и мне), заключалась в том, что .NET по умолчанию использует следующие технологии: LDAP + SSL, Kerberos, затем RPC. Я подозреваю, что RPC отключен в вашей сети (хорошо!), А Kerberos фактически не используется .NET, если вы явно не укажете это с помощью ContextOptions.Negotiate.

Brett Veenstra 21.09.2011 02:37

Я также хочу добавить, что мне нужно было настроить ContextOptions.Negotiate, чтобы исправить проблему с производительностью на нашем производственном сервере. Проверка учетных данных без указания этого перечисления занимала 20–30 секунд.

Justin Helgerson 02.04.2012 01:01

@Sarah - довольно безопасно предположить, что пароль будет передан в метод, содержащий этот код, каким-либо процессом (ввод пользователя и т. д.) - показанный код является всего лишь примером, но вам нужно в какой-то момент передать пароль где-то!

Charleh 01.09.2012 16:43

@Kisame: используйте UserPrincipal.Current

marc_s 17.12.2012 14:05

не текущий пользователь, я вхожу в Windows с определенным именем пользователя, и я хочу войти в приложение с другим именем пользователя

kazem 17.12.2012 14:10

Остерегайтесь - если вы используете это для проверки и у вас есть политика блокировки AD (например, три неправильные попытки), она заблокирует вас.

rbrayb 06.06.2013 23:07

Я получаю сообщение об ошибке: Произошла локальная ошибка. Трассировка стека: в System.DirectoryServices.Protocols.LdapConnection.BindHelper‌ (NetworkCredential newCredential, логическое значение needSetCredential) в System.DirectoryServices.AccountManagement.CredentialValidat‌ или. AccountManagement.CredentialValidat‌ или.BindLdap (кредитные данные NetworkC‌, ContextOptions contextOptions) в System.DirectoryServices.AccountManagement.Crede

variable 20.08.2014 11:13

@marc_s, есть ли способ обрабатывать исключения с помощью этого кода, например incorrect username/pwd, «учетная запись отключена» и т. д.? Спасибо

smr5 20.01.2015 21:25

@Sam: ну, если возвращаемое значение - false - значит, что-то не так (учетные данные недействительны). Я не думаю, что вы можете получить больше информации, чем эта ... (также вы все равно не должны показывать больше пользователю!)

marc_s 20.01.2015 21:29

Если вы получаете это DirectoryOperationException, вместо того, чтобы сразу переходить к Negotiate, как указано в комментариях выше, по крайней мере попробуйте Negotiate | Подписание | Сначала запечатывание. Вы можете подумать, что это то, что он делал по умолчанию без каких-либо параметров (на основе документации в конструкторе PrincipalContext), но если вы используете ILSpy, вы увидите, что по умолчанию он пытается использовать Simple | SecureSocketLayer. И с этим типом исключения он просто терпит неудачу и больше ничего не пытается. Попробуйте с переговорами | Подписание | Запечатывание, и если это сработает, вы все еще в безопасности. Вот что случилось / сработало для меня.

eol 19.03.2015 22:56

Имейте в виду, что если пользователь ИЗМЕНИЛ свой пароль Active Directory, этот фрагмент кода продолжит успешно аутентифицировать пользователя, используя его старый пароль AD. Ага, правда. Прочтите здесь: stackoverflow.com/questions/8949501/…

Mike Gledhill 30.03.2015 12:30

Как я могу получить коды ошибок, такие как stackoverflow.com/a/11033489/206730, с помощью PrincipalContext.ValidateCredentials?

Kiquenet 17.02.2016 14:26

@Kiquenet: вы не можете - ValidateUser будет возвращать только bool (true / false) - не более того

marc_s 17.02.2016 15:40

Не подходит для secure LDAP connection (aka LDAPS, which uses port 636)?

Kiquenet 19.12.2016 18:46

@BrettVeenstra Зачем нужно использовать ContextOptions.Negotiate? Я получаю ошибку The server cannot handle directory requests, используя свой TestAd.aspx. На том же сервере: на веб-сайте возникает ошибка. На другом сайте все работает правильно. Подтвердите оба те же полномочия.

Kiquenet 23.12.2016 20:21

@nzpcmad Вы должны установить политику тайм-аута AD на временную блокировку. Это хорошая практика безопасности для предотвращения взлома учетной записи методом грубой силы.

Shiv 10.01.2017 06:48

@ChrisJ Это хорошая практика безопасности, поскольку вы не хотите предоставлять информацию, если кто-то угадывал учетные записи. Если они получат неправильное имя пользователя или пароль, не сообщайте им, какой из них и срок действия пароля истек. Соблюдайте только допустимые учетные данные. Это защищает вас от сбора имен учетных записей, например, что может быть использовано для попытки взлома через другие векторы.

Shiv 10.01.2017 06:50

@Shiv - но знать, почему что-то не удалось, хорошо и с точки зрения ведения журнала. Я мог бы сказать пользователю «Неверные учетные данные», но могу зарегистрировать на стороне сервера, что «Не удалось войти в систему Боб: истек пароль», а не просто «Не удалось войти в систему Боба».

Chris J 10.01.2017 12:27

@ChrisJ: ну, вы могли бы довольно легко проверить существование имени пользователя и самостоятельно проверить включенный статус. Тогда это будет означать, что проблема была в пароле. Для этого метода имеет смысл не раскрывать информацию об имени пользователя / pw, поскольку очень часто разработчики просто передают сообщение об ошибке. Это поощряет лучшие практики, поскольку используется по умолчанию.

Shiv 12.01.2017 09:11

Если вы застряли с .NET 2.0 и управляемым кодом, вот еще один способ, который работает с локальными и доменными учетными записями:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;

static public bool Validate(string domain, string username, string password)
{
    try
    {
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo()
        {
            FileName = "no_matter.xyz",
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            LoadUserProfile = true,
            Domain = String.IsNullOrEmpty(domain) ? "" : domain,
            UserName = username,
            Password = Credentials.ToSecureString(password)
        };
        proc.Start();
        proc.WaitForExit();
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        switch (ex.NativeErrorCode)
        {
            case 1326: return false;
            case 2: return true;
            default: throw ex;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return false;
}   

Хорошо работает с локальными учетными записями машины, на которой запускается скрипт

eka808 29.11.2011 14:15

Кстати, этот метод необходим, чтобы сделать это общедоступным static SecureString ToSecureString (строка PwString) {char [] PasswordChars = PwString.ToCharArray (); Пароль SecureString = новый SecureString (); foreach (символ c в PasswordChars) Password.AppendChar (c); ProcessStartInfo foo = новый ProcessStartInfo (); foo.Password = Пароль; return foo.Password; }

eka808 29.11.2011 14:19

Напротив, в любом случае следует использовать SecureString для паролей. WPF PasswordBox поддерживает это.

Stephen Drew 10.04.2012 01:07

Еще один вызов .NET для быстрой аутентификации учетных данных LDAP:

using System.DirectoryServices;

using(var DE = new DirectoryEntry(path, username, password)
{
    try
    {
        DE.RefreshCache(); // This will force credentials validation
    }
    catch (COMException ex)
    {
        // Validation failed - handle how you want
    }
}

Это единственное решение, которое сработало для меня, использование PrincipalContext у меня не сработало.

Daniel 16.07.2014 22:36

PrincipalContext недействителен для безопасного соединения LDAP (он же LDAPS, который использует порт 636).

Kiquenet 19.12.2016 18:47

В нескольких представленных здесь решениях отсутствует возможность отличить неправильный пользователь / пароль от пароля, который необходимо изменить. Это можно сделать следующим образом:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

namespace ProtocolTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
                NetworkCredential credential = new NetworkCredential("user", "password");
                connection.Credential = credential;
                connection.Bind();
                Console.WriteLine("logged in");
            }
            catch (LdapException lexc)
            {
                String error = lexc.ServerErrorMessage;
                Console.WriteLine(lexc);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }
    }
}

Если пароль пользователя неверен или пользователь не существует, ошибка будет содержать

"8009030C: LdapErr: DSID-0C0904DC, комментарий: ошибка AcceptSecurityContext, данные 52e, v1db1",

если необходимо изменить пароль пользователя, он будет содержать

«8009030C: LdapErr: DSID-0C0904DC, комментарий: ошибка AcceptSecurityContext, данные 773, v1db1»

Значение данных lexc.ServerErrorMessage - это шестнадцатеричное представление кода ошибки Win32. Это те же коды ошибок, которые были бы возвращены при ином вызове Win32 LogonUser API. В приведенном ниже списке представлен диапазон общих значений с шестнадцатеричными и десятичными значениями:

525​ user not found ​(1317)
52e​ invalid credentials ​(1326)
530​ not permitted to logon at this time​ (1328)
531​ not permitted to logon at this workstation​ (1329)
532​ password expired ​(1330)
533​ account disabled ​(1331) 
701​ account expired ​(1793)
773​ user must reset password (1907)
775​ user account locked (1909)

К сожалению, некоторые установки AD не возвращают дополнительный код ошибки LDAP, что означает, что это решение не будет работать.

Søren Mors 18.06.2012 13:33

Не забудьте добавить ссылки на проект: System.DirectoryServices и System.DirectoryServices.Protocols.

TomXP411 05.04.2013 02:46

Однако у меня такой вопрос: как узнать имя сервера LDAP? Если вы пишете портативное приложение, вы не можете ожидать, что пользователь будет знать или указывать имена серверов AD в каждой сети.

TomXP411 05.04.2013 02:52

У меня есть пользователи, которым разрешен вход на определенные рабочие станции; как указать рабочую станцию, для которой я пытаюсь войти в систему? (например, рабочая станция1 выйдет из строя с данными 531, рабочая станция2 будет работать нормально)

akohlsmith 04.06.2014 01:10

Я чувствую себя странно, потому что не думаю, что тебе достаточно заслуг. Это полностью управляемый метод без проблем с вызовом Win32 API, чтобы определить, «должен ли пользователь сбрасывать пароль», чего явно не достиг ни один из других ответов. Есть ли в этом методе лазейка, из-за которой он не ценится? Хм...

Lionet Chen 08.01.2018 10:36

Спасибо. К сожалению, как я писал в первом комментарии, это может не сработать. Так что это может быть управляемый метод, но он не всегда работает.

Søren Mors 14.02.2018 16:31

Моя простая функция

 private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
    {
        try
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
            DirectorySearcher ds = new DirectorySearcher(de);
            ds.FindOne();
            return true;
        }
        catch //(Exception ex)
        {
            return false;
        }
    }

Проверка подлинности Windows может завершиться ошибкой по разным причинам: неправильное имя пользователя или пароль, заблокированная учетная запись, просроченный пароль и т. д. Чтобы различать эти ошибки, вызовите функцию API LogonUser через P / Invoke и проверьте код ошибки, если функция возвращает false:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

public static class Win32Authentication
{
    private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() // called by P/Invoke
            : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
    }

    private enum LogonType : uint
    {
        Network = 3, // LOGON32_LOGON_NETWORK
    }

    private enum LogonProvider : uint
    {
        WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(
        string userName, string domain, string password,
        LogonType logonType, LogonProvider logonProvider,
        out SafeTokenHandle token);

    public static void AuthenticateUser(string userName, string password)
    {
        string domain = null;
        string[] parts = userName.Split('\\');
        if (parts.Length == 2)
        {
            domain = parts[0];
            userName = parts[1];
        }

        SafeTokenHandle token;
        if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
            token.Dispose();
        else
            throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
    }
}

Пример использования:

try
{
    Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
    // Or: Win32Authentication.AuthenticateUser("[email protected]", "P@ssw0rd");
}
catch (Win32Exception ex)
{
    switch (ex.NativeErrorCode)
    {
        case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
            // ...
        case 1327: // ERROR_ACCOUNT_RESTRICTION
            // ...
        case 1330: // ERROR_PASSWORD_EXPIRED
            // ...
        case 1331: // ERROR_ACCOUNT_DISABLED
            // ...
        case 1907: // ERROR_PASSWORD_MUST_CHANGE
            // ...
        case 1909: // ERROR_ACCOUNT_LOCKED_OUT
            // ...
        default: // Other
            break;
    }
}

Примечание: LogonUser требует доверительных отношений с доменом, в котором вы проверяете.

Вы можете объяснить, почему ваш ответ лучше, чем ответ, получивший наибольшее количество голосов?

Mohammad Ali 17.03.2020 11:28

@MohammadAli: если вам нужно узнать, почему не удалось проверить учетные данные (неверные учетные данные, заблокированная учетная запись, просроченный пароль и т. д.), Функция API LogonUser сообщит вам об этом. Напротив, метод PrincipalContext.ValidateCredentials (согласно комментариям к ответу marc_s) не работает; он возвращает false во всех этих случаях. С другой стороны, LogonUser требует доверительных отношений с доменом, а PrincipalContext.ValidateCredentials (я думаю) - нет.

Michael Liu 17.03.2020 17:17

Вот мое полное решение для проверки подлинности для вашей справки.

Сначала добавьте следующие четыре ссылки

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }

Для меня оба из приведенных ниже сработали, убедитесь, что ваш домен указан с LDAP: // в начале

//"LDAP://" + domainName
private void btnValidate_Click(object sender, RoutedEventArgs e)
{
    try
    {
        DirectoryEntry de = new DirectoryEntry(txtDomainName.Text, txtUsername.Text, txtPassword.Text);
        DirectorySearcher dsearch = new DirectorySearcher(de);
        SearchResult results = null;

        results = dsearch.FindOne();

        MessageBox.Show("Validation Success.");
    }
    catch (LdapException ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
}

private void btnValidate2_Click(object sender, RoutedEventArgs e)
{
    try
    {
        LdapConnection lcon = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
        NetworkCredential nc = new NetworkCredential(txtUsername.Text,
                               txtPassword.Text, txtDomainName.Text);
        lcon.Credential = nc;
        lcon.AuthType = AuthType.Negotiate;
        lcon.Bind(nc);

        MessageBox.Show("Validation Success.");
    }
    catch (LdapException ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
    }
}

Другие вопросы по теме