Проверка учетных данных пользователя в отношении входа в систему windows [duplicate]

Какую версию Windows вы используете (7 или 10)?

Эта проблема, по-видимому, относится к привилегии пользователя ... и если она похожа на эту проблему , сначала попробуйте запустить файл exe с правами администратора, а если он снова исчезнет, ​​попробуйте запустить «Pyinstaller» из cmd.exe , работающего с правами администратора.

460
задан Marc 5 November 2012 в 18:42
поделиться

11 ответов

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

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

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

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

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

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330
28
ответ дан Alan 22 August 2018 в 16:46
поделиться

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

10
ответ дан Charles Bretana 22 August 2018 в 16:46
поделиться
  • 1
    Не используйте обработку исключений для возврата информации из метода. msgstr "Неизвестное имя пользователя или неверный пароль". не является исключительным, это стандартное поведение для LogonUser. Просто верните false. – Treb 14 November 2008 в 17:20
  • 2
    да ... это был порт из старой библиотеки VB6 ... написанный в 2003 году или около того ... (когда .NET впервые вышел) – Charles Bretana 17 November 2008 в 16:18
  • 3
    Если вы работаете в Windows 2000, этот код не будет работать ( support.microsoft.com/kb/180548 ) – Ian Boyd 1 December 2008 в 15:58
  • 4
    Переосмысление этого. Logon Ожидаемое поведение пользователя - это назначение для входа пользователя в систему . Если он не выполняет эту задачу, это IS исключение. На самом деле метод должен возвращать void, а не логическое. Кроме того, если вы только что вернули Boolean, у потребителя метода нет способа сообщить пользователю, в чем причина отказа. – Charles Bretana 24 May 2018 в 12:34

Если вы застряли с .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;
}   
5
ответ дан chauwel 22 August 2018 в 16:46
поделиться
  • 1
    Хорошо работает с локальными учетными записями машины, и он запускает скрипт – eka808 29 November 2011 в 12:15
  • 2
    BTW, этот метод необходим, чтобы заставить это работать public static SecureString ToSecureString (строка PwString) {char [] PasswordChars = PwString.ToCharArray (); SecureString Password = новый SecureString (); foreach (char c в PasswordChars) Password.AppendChar (c); ProcessStartInfo foo = new ProcessStartInfo (); foo.Password = Пароль; return foo.Password; } – eka808 29 November 2011 в 12:19
  • 3
    Напротив, в любом случае следует использовать SecureString для паролей. WPF PasswordBox поддерживает его. – Stephen Drew 9 April 2012 в 22:07

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

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)
44
ответ дан codechurn 22 August 2018 в 16:46
поделиться
  • 1
    К сожалению, некоторые установки AD не возвращают код вспомогательной ошибки LDAP, что означает, что это решение не будет работать. – Søren Mors 18 June 2012 в 10:33
  • 2
    Не забудьте добавить ссылки на проект: System.DirectoryServices и System.DirectoryServices.Protocols – TomXP411 4 April 2013 в 23:46
  • 3
    Мой вопрос, однако, заключается в следующем: как получить имя сервера LDAP? Если вы пишете переносное приложение, вы не можете ожидать, что пользователь узнает или должен указать имена серверов AD в каждой сети. – TomXP411 4 April 2013 в 23:52
  • 4
    У меня есть пользователи, которым запрещено регистрироваться на определенных рабочих станциях; как указать рабочую станцию, на которую я пытаюсь войти? (рабочая станция1 потерпит неудачу с данными 531, рабочая станция2 будет работать нормально, например) – akohlsmith 3 June 2014 в 22:10
  • 5
    Я чувствую себя странно, поскольку не думаю, что у тебя хватит кредитов. Это полностью управляемый метод без проблем вызова Win32 API для определения того, «пользователь должен сбросить пароль», что явно не соответствует ни одному из полученных ответов. Есть ли лазейка в этом методе, что вызывает низкую оценку? хмм ... – Lionet Chen 8 January 2018 в 08:36

Мы делаем это на нашей интрасети

. Вы должны использовать 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();
        }
    }
}
59
ответ дан Community 22 August 2018 в 16:46
поделиться
  • 1
    Не нужно ли этот код запускать как пользователь AD? – bzlm 14 November 2008 в 17:13
  • 2
    Что вы вставляете в «путь»? Имя домена? Имя сервера? Путь LDAP к домену? Путь LDAP к серверу? – Ian Boyd 1 December 2008 в 16:00
  • 3
    Answer1: Нет, мы запускаем его как веб-сервис, поэтому его можно вызывать из нескольких мест в главном веб-приложении. Answer2: Путь содержит информацию LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com – Dining Philanderer 1 December 2008 в 19:21
  • 4
    Ты уверен??? Я получил исключение PrincipalServerdownException, когда я попробовал это ... – Christian Payne 29 May 2009 в 05:59
  • 5
    В моем домене мне пришлось указать pc.ValidateCredentials («myuser», «mypassword», ContextOptions.Negotiate), или я получаю System.DirectoryServices.Protocols.DirectoryOperationException: сервер не может обрабатывать запросы каталога. – Alex Peck 29 June 2011 в 15:14
  • 6
    Если срок действия пароля истек или учетные записи отключены, то ValidateCredentials вернет значение false. К сожалению, он не говорит вам, что почему он возвращает false (что очень жалко, так как это означает, что я не могу сделать что-то разумное, например, перенаправить пользователя на изменение своего пароля). – Chris J 8 September 2011 в 16:10
  • 7
    Также будьте осторожны с учетной записью «Гость», если учетная запись гостевого аккаунта домена включена, ValidateCredentials возвращает true, если вы даете ей несуществующий пользователь. В результате вы можете вызвать UserPrinciple.FindByIdentity, чтобы узнать, существует ли ранее установленный идентификатор пользователя. – Chris J 8 September 2011 в 16:17
  • 8
    @AlexPeck: причина, по которой вам приходилось это делать (например, я), заключалась в том, что .NET по умолчанию использует следующие технологии: LDAP + SSL, Kerberos, а затем RPC. Я подозреваю, что RPC отключен в вашей сети (хорошо!), И Kerberos фактически не используется .NET, если вы явно не указали это с помощью ContextOptions.Negotiate. – Brett Veenstra 20 September 2011 в 23:37
  • 9
    Казалось бы, это может позволить инъекцию LDAP. Вы можете захотеть избежать или удалить любую скобку в strAccountId – Brain2000 9 October 2014 в 16:16
  • 10
    Никогда не должно быть необходимости явно называть Close() переменной using. – Nyerguds 17 May 2016 в 09:28

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

 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;
        }
    }
1
ответ дан hossein andarkhora 22 August 2018 в 16:46
поделиться

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

31
ответ дан Markus Safar 22 August 2018 в 16:46
поделиться
  • 1
    Этот код плохой, потому что он также выполняет проверку авторизации (проверьте, разрешено ли пользователю читать информацию активного каталога). Имя пользователя и пароль могут быть действительными, но пользователю не разрешено читать информацию - и получить исключение. Другими словами, вы можете иметь действительное имя пользователя и пароль, но все равно получить исключение. – Ian Boyd 18 August 2011 в 14:49
  • 2
    Я действительно нахожусь в запросе родного эквивалента PrincipleContext - который существует только в .NET 3.5. Но если вы используете .NET 3.5 или новее, вы должны использовать PrincipleContext – Ian Boyd 18 August 2011 в 17: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, используя предоставленные учетные данные. Если учетные данные недействительны, искатель.FindOne () выдаст исключение. ErrorCode - это тот, который соответствует ошибке COM с недопустимым именем пользователя / паролем.

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

17
ответ дан Mathieu Garstecki 22 August 2018 в 16:46
поделиться
  • 1
    как насчет типов аутентификации? Я думаю, вы забыли его в своем коде выше. :-) по умолчанию DirectoryEntry.AuthenticationType установлен в Защищенное право? этот код не будет работать на LDAP, которые не защищены (анонимно или нет, возможно). Я прав с этим? – jerbersoft 12 November 2010 в 04:47
  • 2
    Сбой для запроса сервера AD заключается в том, что у вас есть разрешение для запроса сервера AD. Ваши учетные данные могут быть действительными, но если у вас нет разрешения на запрос AD, вы получите сообщение об ошибке. Вот почему был создан так называемый Fast Bind ; вы проверяете учетные данные, не разрешая пользователям что-либо делать. – Ian Boyd 19 March 2015 в 15:16
  • 3
    не позволит ли это кому-либо пройти в случае, если COMException будет отправлено по любой другой причине до проверки учетных данных? – Stefan Paul Noack 5 January 2017 в 18:09

Проверка подлинности 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("user@example.com", "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 требуется доверительное отношение с доменом, который вы проверяете.

1
ответ дан Michael Liu 22 August 2018 в 16:46
поделиться

Еще один вызов .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
    }
}
11
ответ дан palswim 22 August 2018 в 16:46
поделиться
  • 1
    Это единственное решение, которое сработало для меня, использование PrincipalContext не сработало для меня. – Daniel 16 July 2014 в 19:36
  • 2
    PrincipalContext недействителен для безопасного соединения LDAP (он же LDAPS, который использует порт 636 – Kiquenet 19 December 2016 в 16:47

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

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

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

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

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

LOGON32_LOGON_NETWORK (3)

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

20
ответ дан stephbu 22 August 2018 в 16:46
поделиться
  • 1
    Как отмечает @Alan, API LogonUser имеет много полезных черт помимо вызова System.DirectoryServices. – stephbu 1 December 2008 в 18:48
  • 2
    @cciotti: Нет, это неправильно. ЛУЧШИЙ способ правильно аутентифицировать кого-то - использовать LogonUserAPI как @stephbu write. Все другие методы, описанные в этом сообщении, НЕ РАБОТАЮТ на 100%. Просто заметьте, однако, я считаю, что вы должны быть присоединены к домену, чтобы вызвать LogonUser. – Alan 20 April 2009 в 19:28
  • 3
    @Alan для создания учетных данных, которые вы должны иметь возможность подключиться к домену, передав действительную учетную запись домена. Однако я уверен, что ваша машина не обязательно должна быть членом домена. – stephbu 21 April 2009 в 03:15
  • 4
    API LogonUser требует, чтобы пользователь имел Act как часть операционной системы privelage; которые не получают пользователи, а не то, что вы хотите предоставить каждому пользователю в организации. ( msdn.microsoft.com/en-us/library/aa378184 (v = vs.85) .aspx [/ д0]) – Ian Boyd 18 August 2011 в 14:52
  • 5
    LogonUser только действует как часть операционной системы для Windows 2000 и ниже в соответствии с support.microsoft.com/kb/180548 ... Он выглядит чистым для Server 2003 и выше. – Chris J 8 September 2011 в 16:14
Другие вопросы по тегам:

Похожие вопросы: