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

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

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

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

问题在于,始终返回一般错误“登录失败:未知用户名或密码错误”。 validation失败时

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

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

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

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

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

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

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

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

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

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

但是,您无法在正在进行身份validation的用户的上下文中调用此方法; 您将需要一个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解决方案。 事实certificate,有一种方法可以从绑定操作中获取扩展错误代码。 这有点难看,但它有效:

 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 (?[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