目前,我验证对一些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的任何财产,同样的错误会被抛出。
的任何其他建议如何获得实际的原因,身份验证失败? (帐户锁定/密码过期/等)
我连接到广告可能不会neccesarily是Windows服务器。
Answer 1:
有点晚,但我会扔了这一点那里。
如果你想真正能够确定该帐户被认证失败的具体原因(还有比错误密码,其他更多的原因,过期,锁定等),你可以使用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);
Answer 2:
我知道这个答案是几年晚,但我们只是碰到了相同的情况下原来的海报。 不幸的是,在我们的环境,我们不能使用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。
Answer 3:
在“密码过期”检查是比较容易的 - 至少在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];
至于“帐户被锁定”检查 - 这似乎很容易在第一,但不是....的“UF_Lockout”标志的“userAccountControl的”不可靠完成其工作。
与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文章就如何处理用户和组在.NET 3.5使用System.DirectoryServices.AccountManagement
命名空间。 例如,你现在确实有一个属性IsAccountLockedOut
在UserPrincipal类可靠地告诉你一个帐号是否被锁定。
希望这可以帮助!
渣
Answer 4:
下面是AD LDAP属性为用户当口令被锁定时与密码没有被锁定的是,改变(第一值)(第二值)。 badPwdCount
和lockoutTime
显然是最相关的。 我不知道是否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
文章来源: Active Directory (LDAP) - Check account locked out / Password expired