Validar un nombre de usuario y contraseña contra Active Directory?

¿Cómo puedo validar un nombre de usuario y contraseña en contra de Active Directory? Simplemente quiero verificar si un nombre de usuario y una contraseña son correctos.

Si trabaja en .NET 3.5 o posterior, puede usar el espacio de nombres System.DirectoryServices.AccountManagement y verificar fácilmente sus credenciales:

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

Es simple, es confiable, es un código administrado 100% C # en su extremo, ¿qué más puede pedir? 🙂

Lea todo al respecto aquí:

  • Administración de los principales de seguridad de directorios en .NET Framework 3.5
  • Documentos de MSDN en System.DirectoryServices.AccountManagement

Actualizar:

Como se describe en esta otra pregunta de SO (y sus respuestas) , hay un problema con esta llamada que posiblemente devuelve True para contraseñas antiguas de un usuario. Solo ten en cuenta este comportamiento y no te sorprendas si esto sucede 🙂 (¡gracias a @MikeGledhill por señalar esto!)

Hacemos esto en nuestra Intranet

Tienes que usar System.DirectoryServices;

Aquí están las entrañas del código

 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(); } } } 

Varias soluciones presentadas aquí carecen de la capacidad de diferenciar entre un usuario / contraseña incorrectos y una contraseña que debe modificarse. Eso se puede hacer de la siguiente manera:

 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); } } } } 

Si la contraseña de los usuarios es incorrecta o el usuario no existe, el error contendrá

“8009030C: LdapErr: DSID-0C0904DC, comentario: error de AcceptSecurityContext, datos 52e, v1db1”,

si la contraseña de los usuarios necesita ser cambiada, contendrá

“8009030C: LdapErr: DSID-0C0904DC, comentario: error de AcceptSecurityContext, datos 773, v1db1”

El valor de datos lexc.ServerErrorMessage es una representación hexadecimal del código de error Win32. Estos son los mismos códigos de error que se devolverían al invocar la llamada a la API Win32 LogonUser. La siguiente lista resume un rango de valores comunes con valores hexadecimales y decimales:

 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) 

solución muy simple usando DirectoryServices:

 using System.DirectoryServices; //srvr = ldap server, eg 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; } 

el acceso de NativeObject es necesario para detectar un usuario / contraseña incorrecto

Lamentablemente, no existe una forma “simple” de verificar las credenciales de un usuario en AD.

Con cada método presentado hasta ahora, puede obtener un falso negativo: los creditos de un usuario serán válidos, sin embargo, AD arrojará los siguientes datos falsos bajo ciertas circunstancias:

  • El usuario debe cambiar la contraseña en el próximo inicio de sesión.
  • La contraseña del usuario ha expirado.

ActiveDirectory no le permitirá usar LDAP para determinar si una contraseña no es válida debido al hecho de que un usuario debe cambiar la contraseña o si su contraseña ha expirado.

Para determinar el cambio de contraseña o la contraseña expiró, puede llamar a Win32: LogonUser () y verificar el código de error de Windows para las siguientes 2 constantes:

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

Probablemente la forma más fácil es invocar LogonUser Win32 API.eg

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

Referencia de MSDN aquí …

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

Definitivamente quiero usar el tipo de inicio de sesión

 LOGON32_LOGON_NETWORK (3) 

Esto crea un token liviano solo, perfecto para verificaciones de AuthN. (otros tipos pueden usarse para crear sesiones interactivas, etc.)

Una solución .Net completa es usar las clases del espacio de nombres System.DirectoryServices. Permiten consultar un servidor AD directamente. Aquí hay una pequeña muestra que haría esto:

 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 

Este código se conecta directamente al servidor AD, utilizando las credenciales proporcionadas. Si las credenciales no son válidas, searcher.FindOne () lanzará una excepción. El ErrorCode es el que corresponde al error COM “nombre de usuario / contraseña inválido”.

No necesita ejecutar el código como un usuario AD. De hecho, lo uso con éxito para consultar información en un servidor AD, desde un cliente fuera del dominio.

Sin embargo, otra llamada .NET para autenticar rápidamente las credenciales 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 } } 

Pruebe este código (NOTA: informa que no funciona en 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 

excepto que deberá crear su propia excepción personalizada para “LogonException”

Si tiene problemas con .NET 2.0 y el código administrado, aquí hay otra manera que funciona con cuentas locales y de dominio:

 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; } 

Mi función simple

  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; } } 

La autenticación de Windows puede fallar por varios motivos: un nombre de usuario o contraseña incorrectos, una cuenta bloqueada, una contraseña caducada y más. Para distinguir entre estos errores, llame a la función LogonUser API a través de P / Invoke y verifique el código de error si la función devuelve 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() } } 

Uso de muestra:

 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; } } 

Nota: LogonUser requiere una relación de confianza con el dominio contra el que está validando.

Intereting Posts