CSharp - 根据活动目录验证用户名和密码?

  显示原文与译文双语对照的内容

如何根据 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# 托管的代码- 你还能问更多? :- )

在这里阅读所有内容:

更新:

就像在中概述的那样,这个问题( 及其答案),这个调用有一个问题,可能返回一个用户的旧密码 True 。 请注意这种行为,如果发生这种情况,不要太惊讶:- ( 感谢 @MikeGledhill 来指出) !

我们在我们的内部网上做这个

你必须使用 System.DirectoryServices ;

下面是代码的要点


DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword);
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.";
 adsEntry.Close();
}
catch (Exception ex)
{
//Failed to authenticate. Most likely it is caused by unknown user
//id or bad strPassword.
 strError = ex.Message;
 adsEntry.Close();
}

使用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访问

不幸的是,没有"简单"方法来检查用户在广告上的凭据。

到目前为止给出的每个方法,你都可以得到一个 false-negative: 用户的凭据将有效,但在某些情况下广告将 return false:

  • 用户在下次登录时需要更改密码。
  • 用户密码已过期。

ActiveDirectory将不允许你使用LDAP来确定密码是否无效,因为用户必须更改密码或者密码已过期。

于以下 2相关constants,来确定更改口令或者密码过期,你可以调用 win32: LogonUser code: ( ),并检查 Windows 错误

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

最简单的方法是连接到 LogonUser 。net Win32 API.e. 。

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

MSDN引用这里。。

http://msdn.microsoft.com/en-us/library/aa378184.aspx

确定要使用登录类型


LOGON32_LOGON_NETWORK (3)

这只创建一个轻量级令牌- 完美的认证。 ( 其他类型可以用于构建交互式会话 等等 )

这里提供几种解决方案,缺乏能够区分出错了用户/密码,一个密码时,需要更改。 可以通过以下方式完成:


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错误,数据 52 e,v1db1",

如果用户的密码需要更改,它将包含

"8009030C: LdapErr: DSID-0C0904DC,注释:AcceptSecurityContext错误,数据 773,v1db1"

lexc.ServerErrorMessage 数据值是Win32错误代码的十六进制表示形式。 这是在调用 Win32 LogonUser API调用时将返回的相同的错误代码。 下面的List 总结了一个具有十六进制和十进制值的公共值范围:


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)

完整的.NET 解决方案是使用 System.DirectoryServices 命名空间中的类。 它们允许直接查询广告服务器。 下面是一个小示例,可以这样做:


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() 将引发异常。 错误代码对应于"无效的用户名/密码"com错误。

你不需要作为广告用户运行代码。 实际上,我成功地使用它来查询一个广告服务器上的信息,来自域之外的客户 !

尝试这里代码( 注意:报告在 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 调用快速验证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
 }
}

如果你使用的是. 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;
} 

...