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

Posted

技术标签:

【中文标题】为啥 Active Directory 验证最后一个密码?【英文标题】:Why does Active Directory validate last password?为什么 Active Directory 验证最后一个密码? 【发布时间】:2012-01-21 00:06:59 【问题描述】:

我正在研究一个简单的解决方案来更新 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?

这与最后一个密码过期有关还是验证应该如何工作?

【问题讨论】:

您的域基础架构有多大?您可能每次都连接到不同的 DC,并且尚未复制新密码。 更新密码后,我无法使用旧密码登录,这很好。只有当我使用 ValidateCredentials() 方法时,它才会为旧密码返回 true :( 可能与缓存的凭据有关? 【参考方案1】:

你看到这个的原因与special behavior specific to NTLM network authentication有关。

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

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

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

    按照我链接的 Microsoft 知识库文章中的说明,使用 OldPasswordAllowedPeriod 注册表值缩短或消除旧密码的生命周期。可能不是最理想的解决方案。 不要使用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 操作,如果凭据错误,这将引发异常。 PrincipalContext 采用相同的方法。

【讨论】:

感谢您的回复。关于这两种方法,我有两个问题。 1) 是否可以在不接触注册表的情况下通过 c# 或 Directive Directory IDE 更新“OldPasswordAllowedPeriod”? 2)所以根据微软的文章,OldPasswordAllowedPeriod 的默认时间(以分钟为单位)是 60 分钟...... Active Directory 中有没有办法确认这一点。 3)您能否建议使用 AuthType.Kerberos 方法验证用户的 C# 代码。获取用户名和密码参数。请回复谢谢。 我添加了一些示例代码。我不知道在使用标准注册表 API 之外设置“OldPasswordAllowedPeriod”注册表值的任何方法。 LdapConnection 实现IDisposable。它应该被包裹在 using 语句中或被放置在 finally 块中。 查看用户是否经过认证后如何获取用户信息?【参考方案2】:

我找到了一种仅验证用户当前凭据的方法。它利用了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
            
        

【讨论】:

【参考方案3】:

根据您如何运行它的上下文,它可能与名为“cached credentials”的东西有关。

【讨论】:

如果与缓存凭据有关...如何通过c#清除密码缓存

以上是关于为啥 Active Directory 验证最后一个密码?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security Active Directory LDAP 身份验证错误

通过 Active Directory 使用 LDAP 在 PHP 中进行身份验证

text Azure Active Directory身份验证

如何集成Apache NiFi和Azure Active Directory以进行用户身份验证?

[Azure - Security] Azure的多重身份验证:使用AD(Azure Active Directory)开启用户MFA

使用Xamarin.iOS进行Azure Active Directory身份验证过程