为什么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验证用户名和密码?

这与上次密码到期有关吗?还是验证应该是这样工作的?

为什么Active Directory验证最后一个密码

您看到这种情况的原因与NTLM网络身份验证特有的特殊行为有关。

PrincipalContext实例上调用ValidateCredentials方法会导致建立安全的LDAP连接,然后使用ldap_bind_s函数调用在该连接上执行绑定操作。

调用ValidateCredentials时使用的身份验证方法是AuthType.Negotiate。使用它会导致使用Kerberos尝试绑定操作,Kerberos(当然是而不是NTLM)将不会表现出上述特殊行为。但是,使用Kerberos的绑定尝试将失败(密码全部错误),这将导致再次尝试,这次使用NTLM。

你有两种方法来解决这个问题:

  1. 按照我链接的Microsoft知识库文章中的说明,使用OldPasswordAllowedPeriod注册表值缩短或消除旧密码的生存期。可能不是最理想的解决方案
  2. 不要使用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
            }
        }

根据运行该程序的上下文,它可能与所谓的"缓存凭据"有关。