Why does Active Directory validate last password?

I am working on a simple solution to update a user's password in Active Directory.

I can successfully update the users password. Updating the password works fine. Lets say the user has updated the password from MyPass1 to MyPass2

Now when I run my custom code to validate users credential using:

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

//returns true - which is good

Now when I enter some wrong password it validates very nicely:

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

//returns false - which is good

Now for some odd reasons, it validates the previous last password which was MyPass1 remember?

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

I got this code from:

Validate a username and password against Active Directory?

Is it something to do with last password expiry or is this how the validation supposed to work?


The reason why you are seeing this has to do with special behavior specific to NTLM network authentication.

Calling the ValidateCredentials method on a PrincipalContext instance results in a secure LDAP connection being made, followed by a bind operation being performed on that connection using a ldap_bind_s function call.

The authentication method used when calling ValidateCredentials is AuthType.Negotiate . Using this results in the bind operation being attempted using Kerberos, which (being not NTLM of course) will not exhibit the special behavior described above. However, the bind attempt using Kerberos will fail (the password being wrong and all), which will result in another attempt being made, this time using NTLM.

You have two ways to approach this:

  • Follow the instructions in the Microsoft KB article I linked to shorten or eliminate the lifetime period of an old password using the OldPasswordAllowedPeriod registry value. Probably not the most ideal solution.
  • Don't use PrincipleContext class to validate credentials. Now that you know (roughly) how ValidateCredentials works, it shouldn't be too difficult for you to do the process manually. What you'll want to do is create a new LDAP connection ( LdapConnection ), set its network credentials, set the AuthType explicitly to AuthType.Kerberos , and then call Bind() . You'll get an exception if the credentials are bad.
  • The following code shows how you can perform credential validation using only Kerberos. The authentication method at use will not fall back to NTLM in the event of failure.

    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;
    }
    

    I try to never use exceptions to handle the flow control of my code; however, in this particular instance, the only way to test credentials on a LDAP connection seems to be to attempt a Bind operation, which will throw an exception if the credentials are bad. PrincipalContext takes the same approach.


    根据你如何运行它的上下文,它可能需要处理一些叫做“缓存凭据”的东西。


    I've found a way to validate the user's current credentials only. It leverages the fact that ChangePassword does not use cached credentials. By attempting to change the password to it's current value, which first validates the password, we can determine if the password is incorrect or there is a policy problem (can't reuse the same password twice).

    Note: this will probably only work if your policy has a history requirement of at least not allowing to repeat the most recent password.

            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
                }
            }
    
    链接地址: http://www.djcxy.com/p/36074.html

    上一篇: 检查用户名/密码

    下一篇: 为什么Active Directory验证最后的密码?