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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…