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

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


person Community    schedule 14.11.2008    source источник


Ответы (14)


Если вы работаете с .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 за указание на это!)

person marc_s    schedule 31.01.2009
comment
Я пробовал: PrincipalContext pc = new PrincipalContext (ContextType.Domain, yourdomain); Ваш пробег может отличаться. - person Christian Payne; 29.05.2009
comment
Кристиан: Вы абсолютно правы. У меня не было под рукой сервера, чтобы проверить мои предположения - и они были неправильными. Вам нужно простое доменное имя, а не дескриптор домена LDAP. Спасибо что подметил это! - person marc_s; 29.05.2009
comment
Это отлично сработало, только для меня мне пришлось добавить к имени пользователя имя домена, то есть pc.ValidateCredentials (DOMAN \\ UserName, Password); - person Michael La Voie; 29.10.2009
comment
@myotherme: да, получите UserPrincipal, а затем используйте user.GetAuthorizationGroups();, чтобы получить этот список. Ознакомьтесь с замечательной статьей MSDN по этой теме: msdn.microsoft.com/en- us / magazine / cc135979.aspx - person marc_s; 12.04.2010
comment
В моем домене мне пришлось указать pc.ValidateCredentials (myuser, mypassword, ContextOptions.Negotiate), иначе я бы получил System.DirectoryServices.Protocols.DirectoryOperationException: сервер не может обрабатывать запросы каталога. - person Alex Peck; 29.06.2011
comment
Если срок действия пароля истек или учетные записи отключены, ValidateCredentials вернет false. К сожалению, он не сообщает вам, почему он вернул false (что очень жаль, поскольку это означает, что я не могу сделать что-то разумное, например, перенаправить пользователя для изменения его пароля). - person Chris J; 08.09.2011
comment
Также будьте осторожны с учетной записью «Гость» - если учетная запись гостя на уровне домена включена, ValidateCredentials вернет true, если вы предоставите ему несуществующего пользователя. В результате вы можете вызвать UserPrinciple.FindByIdentity, чтобы увидеть, существует ли переданный идентификатор пользователя первым. - person Chris J; 08.09.2011
comment
Мы сделали это в нашем домене разработки / тестирования (тот, который случайным образом запускается с ошибками для тестирования различных сценариев). Если бы не это, его бы не поймали, оставив зияющую дыру в безопасности. Проблема в том, что пока у нас есть контроль над нашим собственным доменом, у нас нет никакого контроля над доменом нашего клиента, поэтому я бы предпочел иметь пуленепробиваемый код, чем делать предположения :-) Жалко, что это поведение не было задокументировано в MSDN как потребовался возраст, чтобы выследить ... - person Chris J; 09.09.2011
comment
@AlexPeck: причина, по которой вам пришлось это сделать (как и мне), заключалась в том, что .NET по умолчанию использует следующие технологии: LDAP + SSL, Kerberos, затем RPC. Я подозреваю, что RPC отключен в вашей сети (хорошо!), А Kerberos фактически не используется .NET, если вы явно не укажете это с помощью ContextOptions.Negotiate. - person Brett Veenstra; 21.09.2011
comment
Я также хочу добавить, что мне нужно было установить ContextOptions.Negotiate, чтобы исправить проблему с производительностью на нашем производственном сервере. Проверка учетных данных без указания этого перечисления занимала 20–30 секунд. - person Justin Helgerson; 02.04.2012
comment
@Sarah - довольно безопасно предположить, что пароль будет передан в метод, содержащий этот код, каким-то процессом (ввод пользователя и т. Д.) - показанный код является всего лишь примером, но вам нужно в какой-то момент передать пароль где-нибудь! - person Charleh; 01.09.2012
comment
@Kisame: используйте UserPrincipal.Current - person marc_s; 17.12.2012
comment
не текущий пользователь, я вхожу в систему в Windows по определенному имени пользователя, и я хочу войти в приложение с другим именем пользователя - person kazem; 17.12.2012
comment
Остерегайтесь - если вы используете это для проверки и у вас есть политика блокировки AD (например, три неправильные попытки), она заблокирует вас. - person rbrayb; 06.06.2013
comment
Я получаю сообщение об ошибке: Произошла локальная ошибка. Трассировка стека: в System.DirectoryServices.Protocols.LdapConnection.BindHelper (NetworkCredential newCredential, логическое значение needSetCredential) в System.DirectoryServices.AccountManagement.CredentialValidator.lockedLdapBind (LdapredentialValidator.lockedLdapBind (LdapredentialCredentialValidator.lockedLdapBind (LdapredentialConnection context). (NetworkCredential creds, ContextOptions contextOptions) в System.DirectoryServices.AccountManagement.Crede - person variable; 20.08.2014
comment
@marc_s, есть ли способ обрабатывать исключения с помощью этого кода, например incorrect username/pwd, 'аккаунт отключен' и т. д.? Спасибо - person smr5; 20.01.2015
comment
@Sam: ну, если возвращаемое значение false - значит что-то не так (учетные данные недействительны). Я не думаю, что вы можете получить больше информации, чем эта ... (также вы в любом случае не должны показывать больше пользователю!) - person marc_s; 20.01.2015
comment
Если вы получаете это DirectoryOperationException, вместо того, чтобы сразу переходить к Negotiate, как указано в комментариях выше, по крайней мере попробуйте Negotiate | Подписание | Уплотнение в первую очередь. Вы можете подумать, что это то, что он делал по умолчанию без каких-либо параметров (на основе документации в конструкторе PrincipalContext), но если вы используете ILSpy, вы увидите, что по умолчанию он пытается на самом деле Simple | SecureSocketLayer. И с этим типом исключения он просто терпит неудачу и больше ничего не пытается. Попробуйте с переговорами | Подписание | Запечатывание, и если это сработает, вы все еще в безопасности. Вот что случилось / сработало для меня. - person eol; 19.03.2015
comment
Имейте в виду, что если пользователь ИЗМЕНИЛ свой пароль Active Directory, этот фрагмент кода продолжит успешно аутентифицировать пользователя, используя его старый пароль AD. Ага, правда. Прочтите здесь: http://stackoverflow.com/questions/8949501/why-does-active-directory-validate-last-password - person Mike Gledhill; 30.03.2015
comment
Как я могу получить коды ошибок, например stackoverflow.com/a/11033489/206730, используя PrincipalContext.ValidateCredentials? - person Kiquenet; 17.02.2016
comment
@Kiquenet: вы не можете - ValidateUser будет возвращать только bool (true / false) - не более того - person marc_s; 17.02.2016
comment
Недействительно для secure LDAP connection (aka LDAPS, which uses port 636)? - person Kiquenet; 19.12.2016
comment
@BrettVeenstra Зачем нужно использовать ContextOptions.Negotiate? Я получаю The server cannot handle directory requests ошибку при использовании моего TestAd.aspx. На том же сервере: на веб-сайте возникает ошибка. На другом сайте все работает правильно. Подтвердите одинаковые учетные данные на обоих. - person Kiquenet; 23.12.2016
comment
@nzpcmad Вы должны установить политику тайм-аута AD на временную блокировку. Это хорошая практика безопасности для предотвращения взлома учетной записи методом грубой силы. - person Shiv; 10.01.2017
comment
@ChrisJ Это хорошая практика безопасности, поскольку вы не хотите предоставлять информацию, если кто-то угадывал учетные записи. Если они получат неправильное имя пользователя или пароль, не сообщайте им, какой из них и срок действия пароля истек. Соблюдайте только действительные учетные данные. Это защищает вас от сбора имен учетных записей, например, что может быть использовано для попытки взлома через другие векторы. - person Shiv; 10.01.2017
comment
@Shiv - но знать, почему что-то не удалось, хорошо и с точки зрения ведения журнала. Я могу сообщить пользователю неверные учетные данные, но могу зарегистрировать на стороне сервера, что вход Боба не удался: срок действия пароля истек, а не просто неудачный вход Боба. - person Chris J; 10.01.2017
comment
@ChrisJ: ну, вы могли бы довольно легко проверить существование имени пользователя и самостоятельно проверить включенный статус. Тогда это будет означать, что проблема в пароле. Для этого метода имеет смысл не раскрывать информацию об имени пользователя / pw, поскольку очень часто разработчики просто передают сообщение об ошибке. Это поощряет лучшие практики, поскольку используется по умолчанию. - person Shiv; 12.01.2017

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

Вы должны использовать 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();
        }
    }
}
person DiningPhilanderer    schedule 14.11.2008
comment
Что вы вставляете в путь? Имя домена? Имя сервера? Путь LDAP к домену? Путь LDAP к серверу? - person Ian Boyd; 01.12.2008
comment
Ответ1: Нет, мы запускаем его как веб-службу, поэтому его можно вызывать из разных мест в основном веб-приложении. Ответ2: Путь содержит информацию LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com - person DiningPhilanderer; 01.12.2008
comment
Казалось бы, это может позволить инъекцию LDAP. Возможно, вы захотите избежать или удалить скобки в strAccountId - person Brain2000; 09.10.2014
comment
Означает ли это, что strPassword хранится в LDAP в виде обычного текста? - person Matt Kocaj; 17.07.2015
comment
Никогда не должно быть необходимости явно вызывать Close() для using переменной. - person Nyerguds; 17.05.2016
comment
Мне нужно проверить, а затем получить некоторую информацию о подтвержденном пользователе. этот код работает для меня. Спасибо - person Mazaher Bazari; 03.06.2019
comment
используйте такой путь, как запись. Путь = @LDAP: // OU = Пользователи компании, OU = Компания, DC = boise, DC = esiconstruction, DC = com; Путь идет справа налево, где крайняя правая организационная единица является верхним узлом. - person Golden Lion; 18.06.2020

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

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)
person Søren Mors    schedule 14.06.2012
comment
К сожалению, некоторые установки AD не возвращают дополнительный код ошибки LDAP, что означает, что это решение не будет работать. - person Søren Mors; 18.06.2012
comment
Не забудьте добавить ссылки на проект: System.DirectoryServices и System.DirectoryServices.Protocols - person TomXP411; 05.04.2013
comment
Мой вопрос, однако, таков: как получить имя сервера LDAP? Если вы пишете портативное приложение, вы не можете ожидать, что пользователь будет знать или указывать имена серверов AD в каждой сети. - person TomXP411; 05.04.2013
comment
У меня есть пользователи, которым разрешен вход на определенные рабочие станции; как указать рабочую станцию, для которой я пытаюсь войти в систему? (например, рабочая станция1 выйдет из строя с данными 531, рабочая станция2 будет работать нормально) - person akohlsmith; 04.06.2014
comment
Я чувствую себя странно, потому что не думаю, что тебе достаточно заслуги. Это полностью управляемый метод без проблем с вызовом Win32 API, чтобы определить, должен ли пользователь сбросить пароль, чего явно не удалось достичь ни одним из других ответов. Есть ли в этом методе лазейка, из-за которой он не ценится? Хм... - person Lionet Chen; 08.01.2018
comment
Спасибо. К сожалению, как я писал в первом комментарии, это может не сработать. Так что это может быть управляемый метод, но он не всегда работает. - person Søren Mors; 14.02.2018

очень простое решение с использованием 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 требуется для обнаружения неверного пользователя / пароля

person Steven A. Lowe    schedule 14.11.2008
comment
Этот код плох, потому что он также выполняет проверку авторизации (проверьте, разрешено ли пользователю читать информацию из активного каталога). Имя пользователя и пароль могут быть действительными, но пользователю не разрешено читать информацию - и возникает исключение. Другими словами, у вас могут быть действующие имя пользователя и пароль, но при этом будет исключение. - person Ian Boyd; 18.08.2011
comment
На самом деле я запрашиваю родной эквивалент PrincipleContext, который существует только в .NET 3.5. Но если вы используете .NET 3.5 или новее, вам следует использовать PrincipleContext - person Ian Boyd; 18.08.2011

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

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

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

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

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

  • ERROR_PASSWORD_MUST_CHANGE = 1907.
  • ERROR_PASSWORD_EXPIRED = 1330
person Alan    schedule 14.11.2008
comment
Могу я спросить, где у вас devinitions для Expired и Must_Change ... Нашел их нигде, кроме как здесь :) - person mabstrei; 19.11.2012
comment
Из статьи MSDN: msdn .microsoft.com / en-us / library / windows / desktop / - person Alan; 19.11.2012
comment
Спасибо. Я пытался выяснить, почему моя проверка все время возвращала false. Это произошло потому, что пользователю нужно изменить свой пароль. - person Deise Vicentin; 27.11.2017

Вероятно, самый простой способ - это 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. (другие типы могут использоваться для создания интерактивных сеансов и т. д.)

person stephbu    schedule 14.11.2008
comment
Как указывает @Alan, у LogonUser API есть много полезных свойств, помимо вызова System.DirectoryServices. - person stephbu; 01.12.2008
comment
@cciotti: Нет, это неправильно. ЛУЧШИЙ способ правильно аутентифицировать кого-то - использовать LogonUserAPI как @stephbu write. Все остальные методы, описанные в этом посте, НЕ РАБОТАЮТ на 100%. Замечу, однако, я считаю, что вы должны быть присоединены к домену, чтобы вызвать LogonUser. - person Alan; 20.04.2009
comment
@Alan, чтобы сгенерировать учетные данные, вы должны иметь возможность подключиться к домену, передав действующую учетную запись домена. Однако я почти уверен, что ваша машина не обязательно должна быть членом домена. - person stephbu; 21.04.2009
comment
LogonUser API требует, чтобы у пользователя был уровень Действовать как часть операционной системы; это не то, что получают пользователи, и не то, что вы хотите предоставлять каждому пользователю в организации. (msdn.microsoft.com/en-us/ библиотека / aa378184 (v = vs.85) .aspx) - person Ian Boyd; 18.08.2011
comment
Согласен Иэн, только LogonUser и SSPI могут отображать весь спектр возможных результатов проверки кредита. Выполнение SSPI на C #, как того требует OP, далеко не просто, поэтому рекомендуется LogonUser. Вы видели библиотеку, которая элегантно объединяет ее, не прибегая к неуправляемому коду. Я не смотрел ТБХ. - person stephbu; 24.08.2011
comment
LogonUser требуется только действовать как часть операционной системы для Windows 2000 и ниже в соответствии с support.microsoft.com/kb/180548 ... Он выглядит чистым для Server 2003 и выше. - person Chris J; 08.09.2011
comment
LogonUser не будет работать, если домен не является доменом, которому вы доверяете. Если вы хотите проверить учетные данные домена, вам нужно будет выполнить привязку к LDAP-серверу AD. - person Ian Boyd; 19.03.2015

Полное решение .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 от клиента вне домена!

person Mathieu Garstecki    schedule 14.11.2008
comment
как насчет типов аутентификации? Я думаю, вы забыли это в своем коде выше. :-) по умолчанию для DirectoryEntry.AuthenticationType установлено значение «Защищено», верно? этот код не будет работать с незащищенными LDAP (возможно, Anonymous или None). я правильно с этим? - person jerbersoft; 12.11.2010
comment
Обратной стороной запроса сервера AD является то, что у вас есть разрешение на запрос к серверу AD. Ваши учетные данные могут быть действительными, но если у вас нет разрешения на запрос AD, вы получите сообщение об ошибке. Вот почему был создан так называемый Fast Bind; вы проверяете учетные данные, не разрешая пользователю что-либо делать. - person Ian Boyd; 19.03.2015
comment
не позволит ли это кому-либо пройти, если по какой-либо другой причине будет выброшено исключение COMException до проверки учетных данных? - person Stefan Paul Noack; 05.01.2017

Еще один вызов .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
    }
}
person palswim    schedule 07.04.2011
comment
Это единственное решение, которое сработало для меня, использование PrincipalContext у меня не сработало. - person Daniel; 16.07.2014
comment
PrincipalContext недействителен для безопасного соединения LDAP (он же LDAPS, который использует порт 636). - person Kiquenet; 19.12.2016

Попробуйте этот код (ПРИМЕЧАНИЕ: Сообщается, что не работает на 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»

person Charles Bretana    schedule 14.11.2008
comment
Не используйте обработку исключений для возврата информации из метода. Неизвестное имя пользователя или неверный пароль не являются исключением, это стандартное поведение для LogonUser. Просто верните false. - person Treb; 14.11.2008
comment
да ... это был порт из старой библиотеки VB6 ... написанной в 2003 году или около того ... (когда впервые появился .Net) - person Charles Bretana; 17.11.2008
comment
При запуске в Windows 2000 этот код не будет работать (support.microsoft.com/kb/180548 ) - person Ian Boyd; 01.12.2008
comment
Переосмысление этого. Ожидаемое поведение пользователя входа в систему, его цель - войти в систему. Если ему не удается выполнить эту задачу, это ЯВЛЯЕТСЯ исключением. Фактически, метод должен возвращать void, а не логическое значение. Кроме того, если вы только что вернули логическое значение, у потребителя метода не будет возможности сообщить пользователю, в чем причина сбоя. - person Charles Bretana; 24.05.2018

Проверка подлинности Windows может завершиться ошибкой по разным причинам: неправильное имя пользователя или пароль, заблокированная учетная запись, просроченный пароль и т. Д. Чтобы различать эти ошибки, вызовите 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 требует доверительных отношений с доменом, в котором вы проверяете.

person Michael Liu    schedule 27.12.2016
comment
Вы можете объяснить, почему ваш ответ лучше, чем ответ, получивший наибольшее количество голосов? - person Mohammad Ali; 17.03.2020
comment
@MohammadAli: если вам нужно узнать, почему не удалось выполнить проверку учетных данных (неверные учетные данные, заблокированная учетная запись, просроченный пароль и т. Д.), Функция API LogonUser сообщит вам об этом. Напротив, метод PrincipalContext.ValidateCredentials (согласно комментариям к ответу marc_s) не работает; он возвращает false во всех этих случаях. С другой стороны, LogonUser требует доверительных отношений с доменом, а PrincipalContext.ValidateCredentials (я думаю) - нет. - person Michael Liu; 17.03.2020

Если вы застряли с .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;
}   
person chauwel    schedule 07.04.2011
comment
Хорошо работает с локальными учетными записями машины, на которой он запускает скрипт - person eka808; 29.11.2011
comment
Кстати, этот метод необходим, чтобы сделать это общедоступным статическим SecureString ToSecureString (строка PwString) {char [] PasswordChars = PwString.ToCharArray (); Пароль SecureString = новый SecureString (); foreach (символ c в PasswordChars) Password.AppendChar (c); ProcessStartInfo foo = новый ProcessStartInfo (); foo.Password = Пароль; return foo.Password; } - person eka808; 29.11.2011
comment
Напротив, в любом случае следует использовать SecureString для паролей. WPF PasswordBox поддерживает это. - person Stephen Drew; 10.04.2012

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

 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;
        }
    }
person hossein andarkhora    schedule 27.12.2015

Для меня оба из приведенных ниже сработали, убедитесь, что ваш домен указан с 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}");
    }
}
person dnxit    schedule 16.12.2020

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

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

 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 = "";

        }
    }
person Gayan Chinthaka Dharmarathna    schedule 24.02.2020