Почему Active Directory проверяет последний пароль?
Я работаю над простым решением для обновления пароля пользователя в Active Directory.
Я могу успешно обновить пароль пользователя. Обновление пароля работает нормально. Допустим, пользователь обновил пароль с MyPass1 на MyPass2
Теперь, когда я запускаю свой пользовательский код для проверки учетных данных пользователей, используя:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "MyPass2");
}
//returns true - which is good
Теперь, когда я ввожу неправильный пароль, он очень хорошо проверяет:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "TheDomain"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "wrongPass");
}
//returns false - which is good
Теперь по некоторым странным причинам он проверяет предыдущий последний пароль, который был MyPass1, помните?
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
Я получил этот код от:
Проверить имя пользователя и пароль в Active Directory?
Это как-то связано с истечением срока действия последнего пароля или так должна работать проверка?
3 ответа
Причина, по которой вы видите это, связана со специальным поведением, специфичным для сетевой аутентификации NTLM.
Вызов ValidateCredentials
метод на PrincipalContext
Экземпляр приводит к созданию защищенного соединения LDAP, за которым следует операция связывания, выполняемая для этого соединения с использованием ldap_bind_s
вызов функции.
Метод аутентификации, используемый при звонке ValidateCredentials
является AuthType.Negotiate
, Использование этого приводит к попытке выполнения операции связывания с использованием Kerberos, который (конечно, не NTLM) не будет демонстрировать особого поведения, описанного выше. Однако попытка связывания с использованием Kerberos завершится неудачно (неверный пароль и все), что приведет к другой попытке, на этот раз с использованием NTLM.
У вас есть два подхода к этому:
- Следуйте инструкциям в статье Microsoft KB, на которую я ссылаюсь, чтобы сократить или исключить срок жизни старого пароля, используя значение реестра OldPasswordAllowedPeriod. Наверное, не самое идеальное решение.
- Не использовать
PrincipleContext
класс для проверки учетных данных. Теперь, когда вы знаете (примерно), какValidateCredentials
работает, вам не должно быть слишком сложно выполнить процесс вручную. Что вы хотите сделать, это создать новое соединение LDAP (LdapConnection
), установите его сетевые учетные данные, явно установите AuthTypeAuthType.Kerberos
, а затем позвонитеBind()
, Вы получите исключение, если учетные данные будут плохими.
В следующем коде показано, как выполнить проверку учетных данных, используя только Kerberos. Используемый метод аутентификации не будет использовать NTLM в случае сбоя.
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;
}
Я стараюсь никогда не использовать исключения для управления потоком моего кода; однако в данном конкретном случае единственным способом проверки учетных данных в соединении LDAP, по-видимому, является попытка выполнить операцию Bind, которая выдает исключение, если учетные данные неверны. PrincipalContext
принимает тот же подход.
Я нашел способ проверить только текущие учетные данные пользователя. Это использует тот факт, что ChangePassword
не использует кэшированные учетные данные. Пытаясь изменить пароль на его текущее значение, которое сначала проверяет пароль, мы можем определить, является ли пароль неправильным или есть проблема политики (не может использовать один и тот же пароль дважды).
Примечание: это, вероятно, будет работать только в том случае, если ваша политика имеет требование истории, по крайней мере, не позволяющее повторять самый последний пароль.
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
}
}
В зависимости от контекста того, как вы выполняете это, это может быть связано с тем, что называется " кэшированные учетные данные".