如何针对Active Directory验证用户名和密码?我只是想检查用户名和密码是否正确.
如果您使用的是.NET 3.5或更高版本,则可以使用System.DirectoryServices.AccountManagement
命名空间并轻松验证您的凭据:
// create a "principal context" - e.g. your domain (could be machine, too) using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN")) { // validate the credentials bool isValid = pc.ValidateCredentials("myuser", "mypassword"); }
它很简单,可靠,它是你的100%C#托管代码 - 你还能要求什么?:-)
在这里阅读所有相关内容:
管理.NET Framework 3.5中的目录安全性主体
System.DirectoryServices.AccountManagement上的MSDN文档
更新:
正如其他SO问题(及其答案)中所述,此调用可能会返回True
用户的旧密码.只要注意这种行为,如果发生这种情况就不要太惊讶:-)(感谢@MikeGledhill指出这一点!)
我们在内联网上这样做
你必须使用System.DirectoryServices;
以下是代码的内容
using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword)) { using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry)) { //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))"; adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")"; try { SearchResult adsSearchResult = adsSearcher.FindOne(); bSucceeded = true; strAuthenticatedBy = "Active Directory"; strError = "User has been authenticated by Active Directory."; } catch (Exception ex) { // Failed to authenticate. Most likely it is caused by unknown user // id or bad strPassword. strError = ex.Message; } finally { adsEntry.Close(); } } }
此处介绍的几种解决方案无法区分错误的用户/密码和需要更改的密码.这可以通过以下方式完成:
using System; using System.DirectoryServices.Protocols; using System.Net; namespace ProtocolTest { class Program { static void Main(string[] args) { try { LdapConnection connection = new LdapConnection("ldap.fabrikam.com"); NetworkCredential credential = new NetworkCredential("user", "password"); connection.Credential = credential; connection.Bind(); Console.WriteLine("logged in"); } catch (LdapException lexc) { String error = lexc.ServerErrorMessage; Console.WriteLine(lexc); } catch (Exception exc) { Console.WriteLine(exc); } } } }
如果用户密码错误,或者用户不存在,则会包含错误
"8009030C:LdapErr:DSID-0C0904DC,注释:AcceptSecurityContext错误,数据52e,v1db1",
如果需要更改用户密码,它将包含
"8009030C:LdapErr:DSID-0C0904DC,评论:AcceptSecurityContext错误,数据773,v1db1"
的lexc.ServerErrorMessage
数据值是Win32错误代码的十六进制表示.这些是相同的错误代码,否则将通过调用Win32 LogonUser API调用返回.下面的列表总结了一系列带有十六进制和十进制值的常见值:
525? user not found ?(1317) 52e? invalid credentials ?(1326) 530? not permitted to logon at this time? (1328) 531? not permitted to logon at this workstation? (1329) 532? password expired ?(1330) 533? account disabled ?(1331) 701? account expired ?(1793) 773? user must reset password (1907) 775? user account locked (1909)
使用DirectoryServices的非常简单的解决方案:
using System.DirectoryServices; //srvr = ldap server, e.g. LDAP://domain.com //usr = user name //pwd = user password public bool IsAuthenticated(string srvr, string usr, string pwd) { bool authenticated = false; try { DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd); object nativeObject = entry.NativeObject; authenticated = true; } catch (DirectoryServicesCOMException cex) { //not authenticated; reason why is in cex } catch (Exception ex) { //not authenticated due to some other exception [this is optional] } return authenticated; }
需要NativeObject访问才能检测到错误的用户/密码
遗憾的是,没有"简单"的方法来检查AD上的用户凭据.
对于到目前为止提出的每种方法,您可能会得到假阴性:用户的信用证有效,但在某些情况下AD将返回false:
用户需要在下次登录时更改密码.
用户密码已过期.
ActiveDirectory不允许您使用LDAP来确定密码是否无效,因为用户必须更改密码或密码已过期.
要确定密码更改或密码已过期,您可以调用Win32:LogonUser(),并检查以下2个常量的Windows错误代码:
ERROR_PASSWORD_MUST_CHANGE = 1907
ERROR_PASSWORD_EXPIRED = 1330
可能最简单的方法是PInvoke LogonUser Win32 API.eg
http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html
MSDN参考这里......
http://msdn.microsoft.com/en-us/library/aa378184.aspx
绝对要使用登录类型
LOGON32_LOGON_NETWORK (3)
这仅创建一个轻量级令牌 - 非常适合AuthN检查.(其他类型可用于构建交互式会话等)
完整的.Net解决方案是使用System.DirectoryServices命名空间中的类.它们允许直接查询AD服务器.这是一个小样本,可以做到这一点:
using (DirectoryEntry entry = new DirectoryEntry()) { entry.Username = "here goes the username you want to validate"; entry.Password = "here goes the password"; DirectorySearcher searcher = new DirectorySearcher(entry); searcher.Filter = "(objectclass=user)"; try { searcher.FindOne(); } catch (COMException ex) { if (ex.ErrorCode == -2147023570) { // Login or password is incorrect } } } // FindOne() didn't throw, the credentials are correct
此代码使用提供的凭据直接连接到AD服务器.如果凭据无效,则searcher.FindOne()将抛出异常.ErrorCode是与"无效的用户名/密码"COM错误相对应的错误.
您不需要以AD用户身份运行代码.事实上,我成功地使用它来从域外的客户端查询AD服务器上的信息!
另一个.NET调用快速验证LDAP凭据:
using System.DirectoryServices; using(var DE = new DirectoryEntry(path, username, password) { try { DE.RefreshCache(); // This will force credentials validation } catch (COMException ex) { // Validation failed - handle how you want } }
试试这段代码(注意:报告不能在Windows Server 2000上运行)
#region NTLogonUser #region Direct OS LogonUser Code [DllImport( "advapi32.dll")] private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out int phToken); [DllImport("Kernel32.dll")] private static extern int GetLastError(); public static bool LogOnXP(String sDomain, String sUser, String sPassword) { int token1, ret; int attmpts = 0; bool LoggedOn = false; while (!LoggedOn && attmpts < 2) { LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1); if (LoggedOn) return (true); else { switch (ret = GetLastError()) { case (126): ; if (attmpts++ > 2) throw new LogonException( "Specified module could not be found. error code: " + ret.ToString()); break; case (1314): throw new LogonException( "Specified module could not be found. error code: " + ret.ToString()); case (1326): // edited out based on comment // throw new LogonException( // "Unknown user name or bad password."); return false; default: throw new LogonException( "Unexpected Logon Failure. Contact Administrator"); } } } return(false); } #endregion Direct Logon Code #endregion NTLogonUser
除了你需要为"LogonException"创建自己的自定义异常
如果你坚持使用.NET 2.0和托管代码,这是另一种适用于本地和域帐户的方法:
using System; using System.Collections.Generic; using System.Text; using System.Security; using System.Diagnostics; static public bool Validate(string domain, string username, string password) { try { Process proc = new Process(); proc.StartInfo = new ProcessStartInfo() { FileName = "no_matter.xyz", CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, LoadUserProfile = true, Domain = String.IsNullOrEmpty(domain) ? "" : domain, UserName = username, Password = Credentials.ToSecureString(password) }; proc.Start(); proc.WaitForExit(); } catch (System.ComponentModel.Win32Exception ex) { switch (ex.NativeErrorCode) { case 1326: return false; case 2: return true; default: throw ex; } } catch (Exception ex) { throw ex; } return false; }