繁体   English   中英

Active Directory(LDAP) - 检查帐户被锁定/密码已过期

[英]Active Directory (LDAP) - Check account locked out / Password expired

目前,我使用以下代码针对某些AD对用户进行身份验证:

DirectoryEntry entry = new DirectoryEntry(_path, username, pwd);

try
{
    // Bind to the native AdsObject to force authentication.
    Object obj = entry.NativeObject;

    DirectorySearcher search = new DirectorySearcher(entry) { Filter = "(sAMAccountName=" + username + ")" };
    search.PropertiesToLoad.Add("cn");
    SearchResult result = search.FindOne();
    if (result == null)
    {
        return false;
    }
    // Update the new path to the user in the directory
    _path = result.Path;
    _filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
    throw new Exception("Error authenticating user. " + ex.Message);
}

这非常适合验证用户名的密码。

问题在于总是返回一般错误“登录失败:未知用户名或密码错误”。 验证失败时

但是,当帐户被锁定时,身份验证也可能失败。

我怎么知道它是否因为被锁定而失败?

我遇到过你可以使用的文章:

Convert.ToBoolean(entry.InvokeGet("IsAccountLocked"))

或做一些像这里解释的事情

问题是,每当您尝试访问DirectoryEntry上的任何属性时,都会抛出相同的错误。

关于如何找到认证失败的实际原因的任何其他建议? (帐户被锁定/密码已过期/等)

我连接到的AD可能不一定是Windows服务器。

有点晚了,但我会把它扔出去。

如果您想真正确定帐户验证失败的具体原因(除了错误的密码,过期,锁定等等,还有更多原因),您可以使用Windows API LogonUser。 不要被它吓倒 - 它比看起来容易。 您只需调用LogonUser,如果失败,则查看Marshal.GetLastWin32Error(),它将为您提供返回代码,指示登录失败的(非常)具体原因。

但是,您无法在要进行身份验证的用户的上下文中调用此方法; 您将需要一个priveleged帐户 - 我认为该要求是SE_TCB_NAME(又名SeTcbPrivilege) - 一个有权“作为操作系统的一部分”的用户帐户。

//Your new authenticate code snippet:
        try
        {
            if (!LogonUser(user, domain, pass, LogonTypes.Network, LogonProviders.Default, out token))
            {
                errorCode = Marshal.GetLastWin32Error();
                success = false;
            }
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            CloseHandle(token);    
        }            
        success = true;

如果它失败了,你会得到一个返回代码(还有更多你可以查找,但这些是重要的:

 //See http://support.microsoft.com/kb/155012
    const int ERROR_PASSWORD_MUST_CHANGE = 1907;
    const int ERROR_LOGON_FAILURE = 1326;
    const int ERROR_ACCOUNT_RESTRICTION = 1327;
    const int ERROR_ACCOUNT_DISABLED = 1331;
    const int ERROR_INVALID_LOGON_HOURS = 1328;
    const int ERROR_NO_LOGON_SERVERS = 1311;
    const int ERROR_INVALID_WORKSTATION = 1329;
    const int ERROR_ACCOUNT_LOCKED_OUT = 1909;      //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!!
    const int ERROR_ACCOUNT_EXPIRED = 1793;
    const int ERROR_PASSWORD_EXPIRED = 1330;  

其余的只是复制/粘贴以获取要传入的DLLImports和值

  //here are enums
    enum LogonTypes : uint
        {
            Interactive = 2,
            Network =3,
            Batch = 4,
            Service = 5,
            Unlock = 7,
            NetworkCleartext = 8,
            NewCredentials = 9
        }
        enum LogonProviders : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35,     // sends smoke signals to authority
            WinNT40,     // uses NTLM
            WinNT50      // negotiates Kerb or NTLM
        }

//Paste these DLLImports

[DllImport("advapi32.dll", SetLastError = true)]
        static extern bool LogonUser(
         string principal,
         string authority,
         string password,
         LogonTypes logonType,
         LogonProviders logonProvider,
         out IntPtr token);

[DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr handle);

我知道这个答案已经晚了几年,但我们遇到了与原始海报相同的情况。 不幸的是,在我们的环境中,我们无法使用LogonUser - 我们需要一个纯LDAP解决方案。 事实证明,有一种方法可以从绑定操作中获取扩展错误代码。 这有点难看,但它有效:

catch(DirectoryServicesCOMException exc)
{
    if((uint)exc.ExtendedError == 0x80090308)
    {
        LDAPErrors errCode = 0;

        try
        {
            // Unfortunately, the only place to get the LDAP bind error code is in the "data" field of the 
            // extended error message, which is in this format:
            // 80090308: LdapErr: DSID-0C09030B, comment: AcceptSecurityContext error, data 52e, v893
            if(!string.IsNullOrEmpty(exc.ExtendedErrorMessage))
            {
                Match match = Regex.Match(exc.ExtendedErrorMessage, @" data (?<errCode>[0-9A-Fa-f]+),");
                if(match.Success)
                {
                    string errCodeHex = match.Groups["errCode"].Value;
                    errCode = (LDAPErrors)Convert.ToInt32(errCodeHex, fromBase: 16);
                }
            }
        }
        catch { }

        switch(errCode)
        {
            case LDAPErrors.ERROR_PASSWORD_EXPIRED:
            case LDAPErrors.ERROR_PASSWORD_MUST_CHANGE:
                throw new Exception("Your password has expired and must be changed.");

            // Add any other special error handling here (account disabled, locked out, etc...).
        }
    }

    // If the extended error handling doesn't work out, just throw the original exception.
    throw;
}

并且您需要错误代码的定义( http://www.lifeasbob.com/code/errorcodes.aspx中有更多这些定义):

private enum LDAPErrors
{
    ERROR_INVALID_PASSWORD = 0x56,
    ERROR_PASSWORD_RESTRICTION = 0x52D,
    ERROR_LOGON_FAILURE = 0x52e,
    ERROR_ACCOUNT_RESTRICTION = 0x52f,
    ERROR_INVALID_LOGON_HOURS = 0x530,
    ERROR_INVALID_WORKSTATION = 0x531,
    ERROR_PASSWORD_EXPIRED = 0x532,
    ERROR_ACCOUNT_DISABLED = 0x533,
    ERROR_ACCOUNT_EXPIRED = 0x701,
    ERROR_PASSWORD_MUST_CHANGE = 0x773,
    ERROR_ACCOUNT_LOCKED_OUT = 0x775,
    ERROR_ENTRY_EXISTS = 0x2071,
}

我无法在其他地方找到这些信息 - 每个人都说你应该使用LogonUser。 如果有更好的解决方案,我很乐意听到。 如果没有,我希望这可以帮助其他无法调用LogonUser的人。

“密码过期”检查相对容易 - 至少在Windows上(不确定其他系统如何处理):当“pwdLastSet”的Int64值为0时,用户必须在下一次更改其(或她)密码登录。 检查此问题的最简单方法是在DirectorySearcher中包含此属性:

DirectorySearcher search = new DirectorySearcher(entry)
      { Filter = "(sAMAccountName=" + username + ")" };
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("pwdLastSet");

SearchResult result = search.FindOne();
if (result == null)
{
    return false;
}

Int64 pwdLastSetValue = (Int64)result.Properties["pwdLastSet"][0];

至于“帐户被锁定”检查 - 这看起来很容易,但不是......“userAccountControl”上的“UF_Lockout”标志不能可靠地完成其工作。

从Windows 2003 AD开始,您可以检查一个新的计算属性: msDS-User-Account-Control-Computed

给定DirectoryEntry user ,您可以:

string attribName = "msDS-User-Account-Control-Computed";
user.RefreshCache(new string[] { attribName });

const int UF_LOCKOUT = 0x0010;

int userFlags = (int)user.Properties[attribName].Value;

if(userFlags & UF_LOCKOUT == UF_LOCKOUT) 
{
   // if this is the case, the account is locked out
}

如果你可以使用.NET 3.5,事情变得容易多了 - 请查看MSDN文章 ,了解如何使用System.DirectoryServices.AccountManagement命名空间处理.NET 3.5中的用户和组。 例如,您现在在UserPrincipal类上有一个属性IsAccountLockedOut ,可以可靠地告诉您帐户是否被锁定。

希望这可以帮助!

以下是在锁定密码(第一个值)时密码为用户时更改的AD LDAP属性,而密码未锁定时(第二个值)。 badPwdCountlockoutTime显然是最相关的。 我不确定是否必须手动更新uSNChanged和whenChanged。

$ diff LockedOut.ldif NotLockedOut.ldif

< badPwdCount: 3
> badPwdCount: 0

< lockoutTime: 129144318210315776
> lockoutTime: 0

< uSNChanged: 8064871
> uSNChanged: 8065084

< whenChanged: 20100330141028.0Z
> whenChanged: 20100330141932.0Z

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM