2012-01-21 20 views
32

Estoy trabajando en una solución simple para actualizar la contraseña de un usuario en Active Directory.¿Por qué Active Directory valida la última contraseña?

Puedo actualizar correctamente la contraseña de los usuarios. La actualización de la contraseña funciona bien. Digamos que el usuario ha actualizado la contraseña de MyPass1 a MyPass2

Ahora en que funciono mi código personalizado para validar a los usuarios Credencial usando:

using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain")) 
{ 
    // validate the credentials 
    bool isValid = pc.ValidateCredentials("myuser", "MyPass2"); 
} 

//returns true - which is good 

Ahora, cuando entro en alguna contraseña incorrecta que valida muy bien:

using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain")) 
{ 
    // validate the credentials 
    bool isValid = pc.ValidateCredentials("myuser", "wrongPass"); 
} 

//returns false - which is good 

Ahora, por alguna extraña razón, valida la última contraseña anterior que MyPass1 recuerda?

using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain")) 
{ 
    // validate the credentials 
    bool isValid = pc.ValidateCredentials("myuser", "MyPass1"); 
} 

//returns true - but why? we have updated password to Mypass2 

que tiene el código de:

Validate a username and password against Active Directory?

¿Tiene algo que ver con la última contraseña de caducidad o ¿es así como la validación supone que funciona?

+0

¿Cuán grande es la infraestructura de su dominio? Es posible que se conecte a un DC diferente cada vez y que la nueva contraseña no se haya replicado. –

+0

Después de actualizar la contraseña a una nueva, no puedo iniciar sesión con la contraseña anterior, que es buena. Es solo cuando uso el método ValidateCredentials(), devuelve verdadero para la contraseña anterior :( tal vez algo relacionado con las credenciales almacenadas en caché? – theITvideos

Respuesta

1

Dependiendo del contexto de cómo se ejecuta esto, puede tener que ver con algo llamado "cached credentials".

+0

si se trata de algo relacionado con las credenciales en caché ... ¿Cómo puedo borrarlo? el caché de contraseñas a través de C# – theITvideos

45

La razón por la que está viendo esto tiene que ver con special behavior specific to NTLM network authentication.

Llamar al método ValidateCredentials en un PrincipalContext de instancia da como resultado una conexión LDAP seguro se están realizando, seguido de una operación de enlace curso en dicha conexión mediante una llamada ldap_bind_s función.

El método de autenticación utilizado al llamar al ValidateCredentials es AuthType.Negotiate. El uso de esto da como resultado la operación de vinculación que se intenta con Kerberos, que (siendo no NTLM, por supuesto) no exhibirá el comportamiento especial descrito anteriormente. Sin embargo, el intento de vinculación usando Kerberos fallará (la contraseña es incorrecta y todo), lo que dará como resultado que se realice otro intento, esta vez usando NTLM.

tienes dos maneras de abordar este:

  1. Siga las instrucciones del artículo de Microsoft KB he vinculado a reducir o eliminar el período de vida útil de una vieja contraseña utilizando el valor del registro OldPasswordAllowedPeriod. Probablemente no sea la solución más ideal.
  2. No use la clase PrincipleContext para validar las credenciales. Ahora que sabe (más o menos) cómo funciona ValidateCredentials, no debería ser demasiado difícil para usted hacer el proceso manualmente. Lo que querrá hacer es crear una nueva conexión LDAP (LdapConnection), establecer sus credenciales de red, establecer AuthType explícitamente en AuthType.Kerberos, y luego llamar al Bind(). Obtendrá una excepción si las credenciales son malas.

El siguiente código muestra cómo puede realizar la validación de credenciales utilizando solo Kerberos. El método de autenticación en uso no recurrirá a NTLM en caso de falla.

private const int ERROR_LOGON_FAILURE = 0x31; 

private bool ValidateCredentials(string username, string password, string domain) 
{ 
    NetworkCredential credentials 
    = new NetworkCredential(username, password, domain); 

    LdapDirectoryIdentifier id = new LdapDirectoryIdentifier(domain); 

    using (LdapConnection connection = new LdapConnection(id, credentials, AuthType.Kerberos)) 
    { 
    connection.SessionOptions.Sealing = true; 
    connection.SessionOptions.Signing = true; 

    try 
    { 
     connection.Bind(); 
    } 
    catch (LdapException lEx) 
    { 
     if (ERROR_LOGON_FAILURE == lEx.ErrorCode) 
     { 
     return false; 
     } 
     throw; 
    } 
    } 
    return true; 
} 

Intento nunca utilizar excepciones para controlar el flujo de mi código; sin embargo, en esta instancia particular, la única forma de probar las credenciales en una conexión LDAP parece ser intentar una operación de vinculación, que lanzará una excepción si las credenciales son malas. PrincipalContext tiene el mismo enfoque.

+0

Gracias por su respuesta. Tengo dos preguntas con respecto a los 2 enfoques. – theITvideos

+0

1) ¿Es posible actualizar el 'OldPasswordAllowedPeriod' a través de C# o directorio directivo IDE sin tocar el registro? 2) De acuerdo con el artículo de Microsoft, el tiempo predeterminado en minutos para OldPasswordAllowedPeriod es de 60 minutos ... ¿hay alguna manera en el directorio activo para confirmar esto? 3) ¿Puede sugerir un código C# que valida al usuario utilizando el método AuthType.Kerberos. Tomando los parámetros de nombre de usuario y contraseña. Responda Gracias. – theITvideos

+0

Agregué un código de muestra. No conozco ninguna forma de establecer el valor de registro 'OldPasswordAllowedPeriod' fuera del uso de la API de registro estándar. –

1

He encontrado una forma de validar las credenciales actuales del usuario solamente. Aprovecha el hecho de que ChangePassword no usa credenciales en caché. Al intentar cambiar la contraseña a su valor actual, que primero valida la contraseña, podemos determinar si la contraseña es incorrecta o si hay un problema de política (no puede reutilizar la misma contraseña dos veces).

Nota: esto probablemente solo funcione si su política tiene un requisito de historial de al menos no permitir que se repita la contraseña más reciente.

 var isPasswordValid = PrincipalContext.ValidateCredentials(
      userName, 
      password); 

     // use ChangePassword to test credentials as it doesn't use caching, unlike ValidateCredentials 
     if (isPasswordValid) 
     { 
      try 
      { 
       user.ChangePassword(password, password); 
      } 
      catch (PasswordException ex) 
      { 
       if (ex.InnerException != null && ex.InnerException.HResult == -2147024810) 
       { 
        // Password is wrong - must be using a cached password 
        isPasswordValid = false; 
       } 
       else 
       { 
        // Password policy problem - this is expected, as we can't change a password to itself for history reasons  
       } 
      } 
      catch (Exception) 
      { 
       // ignored, we only want to check wrong password. Other AD related exceptions should occure in ValidateCredentials 
      } 
     } 
Cuestiones relacionadas