c# - usuario - ¿Por qué Active Directory valida la última contraseña?
single sign on c# active directory (3)
Estoy trabajando en una solución simple para actualizar la contraseña de un usuario en Active Directory.
Puedo actualizar con éxito 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 cuando ejecuto mi código personalizado para validar las credenciales de los usuarios 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 ingreso una contraseña incorrecta, 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
Obtuve este código de:
Validar un nombre de usuario y contraseña contra Active Directory?
¿Tiene algo que ver con la última expiración de contraseñas o así es como se supone que funciona la validación?
Dependiendo del contexto de cómo ejecute esto, puede tener que ver con algo llamado " credenciales en caché ".
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
}
}
La razón por la que está viendo esto tiene que ver con un comportamiento especial específico para la autenticación de red NTLM .
Llamar al método ValidateCredentials
en una instancia de PrincipalContext
genera una conexión LDAP segura, seguida de una operación de vinculación que se realiza en esa conexión mediante una ldap_bind_s
función ldap_bind_s
.
El método de autenticación utilizado al llamar a ValidateCredentials
es AuthType.Negotiate
. El uso de esto da como resultado la operación de vinculación que se intenta con Kerberos, que (al no ser 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 formas de acercarte a esto:
- Siga las instrucciones del artículo de Microsoft KB que he vinculado para acortar o eliminar el período de vigencia de una contraseña anterior utilizando el valor de registro OldPasswordAllowedPeriod . Probablemente no sea la solución más ideal.
- No use la clase
PrincipleContext
para validar las credenciales. Ahora que sabe (más o menos) cómo funcionaValidateCredentials
, 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 enAuthType.Kerberos
, y luego llamar aBind()
. 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 usar excepciones para manejar el control de 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.