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

时间:2012-01-21 00:06:59

标签: 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

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

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

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

Validate a username and password against Active Directory?

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

3 个答案:

答案 0 :(得分:51)

您看到这一点的原因与special behavior specific to NTLM network authentication有关。

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

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

您有两种方法可以解决这个问题:

  1. 按照我链接的Microsoft知识库文章中的说明,使用 OldPasswordAllowedPeriod 注册表值缩短或消除旧密码的生命周期。可能不是最理想的解决方案。
  2. 不要使用PrincipleContext类来验证凭据。既然您已经(大致)知道ValidateCredentials的工作原理,那么您手动执行此过程应该不会太困难。您要做的是创建新的LDAP连接(LdapConnection),设置其网络凭据,将AuthType明确设置为AuthType.Kerberos,然后调用Bind()。如果凭据不好,您将收到异常。
  3. 以下代码显示了如何仅使用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连接上测试凭据的唯一方法似乎是尝试绑定操作,如果凭据不正确,则会抛出异常。 PrincipalContext采用相同的方法。

答案 1 :(得分:1)

根据您如何运行此操作的上下文,它可能与名为“cached credentials”的内容有关。

答案 2 :(得分:1)

我找到了一种方法来验证用户当前的凭据。它利用 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 } } 不使用缓存凭据的事实。通过尝试将密码更改为其当前验证密码的当前值,我们可以确定密码是否不正确或是否存在策略问题(无法重复使用相同的密码两次)。

注意:这可能仅在您的政策的历史记录要求至少不允许重复最新密码时才有效。

 var date = new Date(parseInt(data.UPDATED_ON.substr(6)));
 alert(date.getDate() + '/' + (date.getMonth() + 1) + '/' +  date.getFullYear());