为什么Active Directory验证最后一个密码
本文关键字:最后一个 密码 验证 Directory Active 为什么 | 更新日期: 2023-09-27 18:21:03
我正在开发一个简单的解决方案来更新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验证用户名和密码?
这与上次密码到期有关吗?还是验证应该是这样工作的?
您看到这种情况的原因与NTLM网络身份验证特有的特殊行为有关。
在PrincipalContext
实例上调用ValidateCredentials
方法会导致建立安全的LDAP连接,然后使用ldap_bind_s
函数调用在该连接上执行绑定操作。
调用ValidateCredentials
时使用的身份验证方法是AuthType.Negotiate
。使用它会导致使用Kerberos尝试绑定操作,Kerberos(当然是而不是NTLM)将不会表现出上述特殊行为。但是,使用Kerberos的绑定尝试将失败(密码全部错误),这将导致再次尝试,这次使用NTLM。
你有两种方法来解决这个问题:
- 按照我链接的Microsoft知识库文章中的说明,使用OldPasswordAllowedPeriod注册表值缩短或消除旧密码的生存期。可能不是最理想的解决方案
- 不要使用
PrincipleContext
类来验证凭据。现在您已经(大致)了解了ValidateCredentials
的工作原理,手动执行该过程应该不会太困难。您要做的是创建一个新的LDAP连接(LdapConnection
),设置其网络凭据,将AuthType显式设置为AuthType.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操作,如果凭据不正确,该操作将引发异常。CCD_ 11采取了相同的方法。
我找到了一种只验证用户当前凭据的方法。它利用了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
}
}
根据运行该程序的上下文,它可能与所谓的"缓存凭据"有关。