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

the*_*eos 33 c# 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
Run Code Online (Sandbox Code Playgroud)

现在,当我输入一些错误的密码时,它会很好地验证:

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

//returns false - which is good
Run Code Online (Sandbox Code Playgroud)

现在由于一些奇怪的原因,它验证了以前的最后一个密码是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
Run Code Online (Sandbox Code Playgroud)

我从以下代码获得此代码:

验证Active Directory的用户名和密码?

它与上次密码到期有关,或者这是验证应该如何工作?

小智 52

您看到这一点的原因与特定于NTLM网络身份验证的特殊行为有关.

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

打电话时使用的身份验证方法ValidateCredentialsAuthType.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;
}
Run Code Online (Sandbox Code Playgroud)

我尝试永远不会使用异常来处理我的代码的流控制; 但是,在此特定实例中,在LDAP连接上测试凭据的唯一方法似乎是尝试绑定操作,如果凭据不正确,则会抛出异常.PrincipalContext采取相同的方法.