2009-09-08 18 views
16

Aktualnie I uwierzytelniać użytkowników przed jakimś AD za pomocą następującego kodu:Active Directory (LDAP) - sprawdź konto zablokowane/hasło wygasło

DirectoryEntry entry = new DirectoryEntry(_path, username, pwd); 

try 
{ 
    // Bind to the native AdsObject to force authentication. 
    Object obj = entry.NativeObject; 

    DirectorySearcher search = new DirectorySearcher(entry) { Filter = "(sAMAccountName=" + username + ")" }; 
    search.PropertiesToLoad.Add("cn"); 
    SearchResult result = search.FindOne(); 
    if (result == null) 
    { 
     return false; 
    } 
    // Update the new path to the user in the directory 
    _path = result.Path; 
    _filterAttribute = (String)result.Properties["cn"][0]; 
} 
catch (Exception ex) 
{ 
    throw new Exception("Error authenticating user. " + ex.Message); 
} 

Działa to doskonale do sprawdzania hasła przed nazwą użytkownika.

Problem polega na tym, że generyczne błędy są zawsze zwracane "Błąd logowania: nieznana nazwa użytkownika lub złe hasło." gdy uwierzytelnianie nie powiedzie się.

Jednak autoryzacja może również zawieść, gdy konto zostanie zablokowane.

Skąd mam wiedzieć, czy nie działa z powodu zablokowania?

Natknąłem artykułów mówiąc można użyć:

Convert.ToBoolean(entry.InvokeGet("IsAccountLocked")) 

lub zrobić coś jak wyjaśniono here

jest problem, gdy próby uzyskania dostępu do dowolnej właściwości na DirectoryEntry, ten sam błąd zostałby rzucony.

Jakieś inne sugestie, jak dojść do rzeczywistej przyczyny niepowodzenia uwierzytelnienia? (konto zablokowane/hasło wygasło/itp.)

AD, z którym się łączę, niekoniecznie musi być serwerem Windows.

Odpowiedz

15

Trochę za późno, ale wyrzucę to tam.

Jeśli chcesz NAPRAWDĘ być w stanie określić konkretny powód niepowodzenia uwierzytelniania konta (jest wiele innych powodów niż złe hasło, wygasło, blokowanie itp.), Możesz użyć LogonUser interfejsu API systemu Windows. Nie daj się zastraszyć - jest to łatwiejsze niż się wydaje. Po prostu wywołujesz LogonUser, a jeśli ci się nie uda, spójrz na Marszałka.GetLastWin32Error(), który da ci kod powrotu wskazujący (bardzo) konkretny powód niepowodzenia logowania.

Nie będzie można jednak wywoływać tego w kontekście użytkownika, którego tożsamość jest uwierzytelniana; będziesz potrzebować konta z zastrzeżonymi uprawnieniami - uważam, że wymaganiem jest SE_TCB_NAME (vel SeTcbPrivilege) - konto użytkownika, które ma prawo "działać jako część systemu operacyjnego".

//Your new authenticate code snippet: 
     try 
     { 
      if (!LogonUser(user, domain, pass, LogonTypes.Network, LogonProviders.Default, out token)) 
      { 
       errorCode = Marshal.GetLastWin32Error(); 
       success = false; 
      } 
     } 
     catch (Exception) 
     { 
      throw; 
     } 
     finally 
     { 
      CloseHandle(token);  
     }    
     success = true; 

jeśli to się nie powiedzie, pojawi się jeden z kodów zwrotnych (istnieją bardziej, że można spojrzeć w górę, ale są to Najważniejszymi:

//See http://support.microsoft.com/kb/155012 
    const int ERROR_PASSWORD_MUST_CHANGE = 1907; 
    const int ERROR_LOGON_FAILURE = 1326; 
    const int ERROR_ACCOUNT_RESTRICTION = 1327; 
    const int ERROR_ACCOUNT_DISABLED = 1331; 
    const int ERROR_INVALID_LOGON_HOURS = 1328; 
    const int ERROR_NO_LOGON_SERVERS = 1311; 
    const int ERROR_INVALID_WORKSTATION = 1329; 
    const int ERROR_ACCOUNT_LOCKED_OUT = 1909;  //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!! 
    const int ERROR_ACCOUNT_EXPIRED = 1793; 
    const int ERROR_PASSWORD_EXPIRED = 1330; 

reszta jest po prostu kopiuj/wklej do uzyskać DLLImports i wartości przechodzą w

//here are enums 
    enum LogonTypes : uint 
     { 
      Interactive = 2, 
      Network =3, 
      Batch = 4, 
      Service = 5, 
      Unlock = 7, 
      NetworkCleartext = 8, 
      NewCredentials = 9 
     } 
     enum LogonProviders : uint 
     { 
      Default = 0, // default for platform (use this!) 
      WinNT35,  // sends smoke signals to authority 
      WinNT40,  // uses NTLM 
      WinNT50  // negotiates Kerb or NTLM 
     } 

//Paste these DLLImports 

[DllImport("advapi32.dll", SetLastError = true)] 
     static extern bool LogonUser(
     string principal, 
     string authority, 
     string password, 
     LogonTypes logonType, 
     LogonProviders logonProvider, 
     out IntPtr token); 

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

Dziękuję za to. Znalazłem testowanie na serwerze Windows 2008 AD, który dla wygasłych, ale ważnych haseł, wynik będzie "ERROR_PASSWORD_MUST_CHANGE", ale jeśli hasło wygasło i podane hasło jest nieważne, wynikiem będzie 'ERROR_LOGON_FAILURE'. – Alpha

+0

Zobacz moją odpowiedź (http://stackoverflow.com/a/16796531/1230982), jeśli możesz" t używaj LogonUser i potrzebujesz rozwiązania LDAP –

4

Kontrola "hasła wygasa" jest względnie łatwa - przynajmniej w systemie Windows (nie wiesz, jak radzą sobie z tym inne systemy): kiedy wartość Int64 "pwdLastSet" wynosi 0, wówczas użytkownik będzie musiał zmienić jego (lub ją)) hasło przy następnym logowaniu. Najprostszym sposobem na sprawdzenie tego jest to nieruchomość w swoim DirectorySearcher:

DirectorySearcher search = new DirectorySearcher(entry) 
     { Filter = "(sAMAccountName=" + username + ")" }; 
search.PropertiesToLoad.Add("cn"); 
search.PropertiesToLoad.Add("pwdLastSet"); 

SearchResult result = search.FindOne(); 
if (result == null) 
{ 
    return false; 
} 

Int64 pwdLastSetValue = (Int64)result.Properties["pwdLastSet"][0]; 

Co do „Konto jest zablokowane” check - to wydaje się łatwe na początku, ale nie jest .... W „UF_Lockout” flaga na "userAccountControl" nie działa niezawodnie.

Począwszy od Windows 2003 AD, jest nowy wyliczony atrybut, który można sprawdzić na: msDS-User-Account-Control-Computed.

Biorąc pod uwagę DirectoryEntry user, można zrobić:

string attribName = "msDS-User-Account-Control-Computed"; 
user.RefreshCache(new string[] { attribName }); 

const int UF_LOCKOUT = 0x0010; 

int userFlags = (int)user.Properties[attribName].Value; 

if(userFlags & UF_LOCKOUT == UF_LOCKOUT) 
{ 
    // if this is the case, the account is locked out 
} 

Jeśli można użyć .NET 3.5, rzeczy zdobyć dużo łatwiejsze - zobacz MSDN article, w jaki sposób radzić sobie z użytkownikami i grupami w .NET 3.5 przy użyciu przestrzeni nazw System.DirectoryServices.AccountManagement. Na przykład. masz teraz właściwość IsAccountLockedOut na klasie UserPrincipal, która niezawodnie informuje, czy konto jest zablokowane.

Mam nadzieję, że to pomoże!

Marc

+0

Dzięki marc ... spróbuję. Czy jednak System.DirectoryServices.AccountManagement w .NET 3.5 nie ogranicza mnie do aktywnych katalogów Windows? A może nadal stosuje tych samych kierowników LDAP? – Jabezz

+0

Ah przepraszam - tak, S.DS.AM jest specyficzne dla Active Directory, przepraszam. Ale istnieje również "niskopoziomowa" biblioteka LDAP w przestrzeni nazw System.DirectoryServices.Protocols, od .NET 2.0 (jak sądzę) –

+0

Cześć Marc, wypróbowałem te sugestie, ale wciąż pracuję nad tym samym problemem. Nie mogę nawet zastosować usługi DirectorySearcher, jeśli przekażę nazwę użytkownika/pwd do konstruktora DirectoryEntry, ponieważ uwierzytelnianie zakończy się niepowodzeniem, jeśli konto zostanie zablokowane. Jeśli go nie przepuszczę, mogę przeprowadzić wyszukiwanie, ale nie dostaję żadnej z wymienionych właściwości. "pwdLastSet" nie istnieje, a user.Properties jest zawsze pusta. Chyba będę musiał umieścić to na lodzie na jakiś czas. – Jabezz

1

Oto LDAP AD atrybuty zmiany dla użytkownika, gdy hasło jest zablokowane (pierwsza wartość) w porównaniu gdy hasło nie jest zablokowane (wartość sekundę). badPwdCount i lockoutTime są oczywiście najbardziej istotne. Nie jestem pewien, czy uSNChanged i whenChanged muszą być aktualizowane ręcznie lub nie.

$ diff LockedOut.ldif NotLockedOut.ldif:

< badPwdCount: 3 
> badPwdCount: 0 

< lockoutTime: 129144318210315776 
> lockoutTime: 0 

< uSNChanged: 8064871 
> uSNChanged: 8065084 

< whenChanged: 20100330141028.0Z 
> whenChanged: 20100330141932.0Z 
6

wiem, ta odpowiedź jest kilka lat spóźnione, ale my po prostu wpadł na takiej samej sytuacji jak oryginalnego plakatu. Niestety w naszym środowisku nie możemy używać LogonUser - potrzebowaliśmy czystego rozwiązania LDAP. Okazuje się, że istnieje sposób uzyskania rozszerzonego kodu błędu z operacji wiązania. To trochę brzydki, ale to działa:

catch(DirectoryServicesCOMException exc) 
{ 
    if((uint)exc.ExtendedError == 0x80090308) 
    { 
     LDAPErrors errCode = 0; 

     try 
     { 
      // Unfortunately, the only place to get the LDAP bind error code is in the "data" field of the 
      // extended error message, which is in this format: 
      // 80090308: LdapErr: DSID-0C09030B, comment: AcceptSecurityContext error, data 52e, v893 
      if(!string.IsNullOrEmpty(exc.ExtendedErrorMessage)) 
      { 
       Match match = Regex.Match(exc.ExtendedErrorMessage, @" data (?<errCode>[0-9A-Fa-f]+),"); 
       if(match.Success) 
       { 
        string errCodeHex = match.Groups["errCode"].Value; 
        errCode = (LDAPErrors)Convert.ToInt32(errCodeHex, fromBase: 16); 
       } 
      } 
     } 
     catch { } 

     switch(errCode) 
     { 
      case LDAPErrors.ERROR_PASSWORD_EXPIRED: 
      case LDAPErrors.ERROR_PASSWORD_MUST_CHANGE: 
       throw new Exception("Your password has expired and must be changed."); 

      // Add any other special error handling here (account disabled, locked out, etc...). 
     } 
    } 

    // If the extended error handling doesn't work out, just throw the original exception. 
    throw; 
} 

i trzeba definicje kodów błędów (są tam o wiele więcej z nich w http://www.lifeasbob.com/code/errorcodes.aspx):

private enum LDAPErrors 
{ 
    ERROR_INVALID_PASSWORD = 0x56, 
    ERROR_PASSWORD_RESTRICTION = 0x52D, 
    ERROR_LOGON_FAILURE = 0x52e, 
    ERROR_ACCOUNT_RESTRICTION = 0x52f, 
    ERROR_INVALID_LOGON_HOURS = 0x530, 
    ERROR_INVALID_WORKSTATION = 0x531, 
    ERROR_PASSWORD_EXPIRED = 0x532, 
    ERROR_ACCOUNT_DISABLED = 0x533, 
    ERROR_ACCOUNT_EXPIRED = 0x701, 
    ERROR_PASSWORD_MUST_CHANGE = 0x773, 
    ERROR_ACCOUNT_LOCKED_OUT = 0x775, 
    ERROR_ENTRY_EXISTS = 0x2071, 
} 

nie mogłem znaleźć te informacje nigdzie indziej - wszyscy mówią, że powinieneś używać LogonUser. Jeśli jest lepsze rozwiązanie, chciałbym to usłyszeć. Jeśli nie, mam nadzieję, że pomoże to innym osobom, które nie mogą połączyć się z LogonUser.

+0

Oprócz linku, znalazłem ten odnośnik MS pod ręką: https://support.microsoft.com/en-us/kb/155012 –

+0

nawet jeśli konto jest zablokowane, nadal otrzymuję tylko COMException, a nie DirectoryServicesC OMException. Ta metoda nie działa. –

Powiązane problemy