Winapi PrincipalContext.ValidateCredentials的本地等效项是什么?

Winapi PrincipalContext.ValidateCredentials的本地等效项是什么?,winapi,active-directory,credentials,Winapi,Active Directory,Credentials,针对该域验证一组用户域凭据(用户名、密码、域)的本机方法是什么 换言之,我正在寻找与之相对应的本土产品: Boolean ValidateCredentials(String username, String password, String domain) { // create a "principal context" - e.g. your domain (could be machine, too) using(PrincipalContext pc = new Princ

针对该域验证一组用户域凭据(用户名、密码、域)的本机方法是什么

换言之,我正在寻找与之相对应的本土产品:

Boolean ValidateCredentials(String username, String password, String domain)
{
   // create a "principal context" - e.g. your domain (could be machine, too)
   using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
   {
      // validate the credentials
      return pc.ValidateCredentials(username, password)
   }
}

ValidateCredentials("iboyd", "Tr0ub4dor&3", "contoso");
这不是被问到了吗,回答到了,死了吗? 不!这个问题被问了很多。其中三次是我干的。但是你越深入研究,你就越意识到被接受的答案是错误的

微软设法在.NET中解决了这个问题,在.NET3.5中添加了PrincipalContext类。而原则性的内容并不是什么神奇的东西。在下面,它使用扁平的C风格
ldap
API。但是试图对ILSpy的代码进行反向工程是行不通的。尽管有referencesource,微软仍然对.NETFramework类库的大部分源代码保密

你试过什么? 方法1:只需使用
登录用户
我无法使用
登录用户
。LogonUser仅在您的计算机位于您正在验证的域上(例如
contoso
)或您的域信任您正在验证的域时工作。换句话说,如果有一个域控制器用于contoso.test域,那么:

LogonUser("iboyd", "contoso.test", "Tr0ub4dor&3", 
      LOGON32_LOGON_NETWORK, "Negotiate", ref token);
将失败并出现错误:

1326(登录失败:未知用户名或错误密码)

这是因为我指定的域不是我自己的域,也不是我信任的域

C#
PrincipalContext
不会遇到此问题

方法2:只需使用SSPI(安全支持提供商接口) SSPI是
LogonUser
内部使用的。简而言之,它失败的原因与
LogonUser
失败的原因相同:Windows将不信任来自不受信任域的凭据

代码相当长,提供了一个示例。psuedo代码jist为:

QuerySecurityPackageInfo("Negotiate");

// Prepare client message (negotiate)
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context

// Prepare server message (challenge).    
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context

// Prepare client message (authenticate).
AcquireCredentialsHandle(....); //for the client
InitializeSecurityContext(...); //on the returned client handle
CompleteAuthToken(...); //on the client context

// Prepare server message (authentication).
AcquireCredentialsHandle(...); //for the server
AcceptSecurityContext(...); //on the returned server handle
CompleteAuthToken(...); //on the server context
如果您的计算机加入到您正在验证凭据的域,则此代码非常有效。但是,一旦您尝试验证来自外部域的一组域Credentail,它就会失败

C#
PrincipalContext
不会遇到此问题

方法3:只需使用LDAP 有些人可能建议使用

这是一种误导,因为
AdsGetObject
不支持传递用户名/密码:

HRESULT ADsGetObject(
  _In_   LPCWSTR lpszPathName,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);
相反,您只需询问用户的情况

也许您的意思是
adopenobject

HRESULT ADsOpenObject(
  _In_   LPCWSTR lpszPathName,
  _In_   LPCWSTR lpszUserName,
  _In_   LPCWSTR lpszPassword,
  _In_   DWORD dwReserved,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);
您可以在其中指定要连接的凭据

C#
PrincipalContext
不会遇到此问题

方法4:只需使用
AdsOpenObject
有些人可能建议使用:

撇开我构造的路径无效这一事实不谈,撇开当您只知道以下情况时无法构造有效的LDAP路径这一事实不谈:

  • {username}
  • {密码}
  • {域}
那是因为

LDAP://CN=
{username}
,DC=
{domain}

不是任何用户的有效LDAP路径

尽管存在LDAP路径难题,但基本问题是尝试查询LDAP。这是错误的

我们希望验证用户的广告凭证

每当我们尝试查询LDAP服务器时,如果用户没有查询LDAP的权限,即使他们的凭据有效,我们也会失败

当您将凭据传递给
AdsOpenObject
时,它们用于指定您要连接的用户。连接后,将对LDAP执行查询。当您没有查询LDAP的权限时,AdsOpenObject将失败

更令人恼火的是,即使您确实有权在LDAP中查询用户,您仍然不必要地执行LDAP查询—这是一项昂贵的操作

C#
PrincipalContext
不会遇到此问题

方法5:将ADO与ADsDSOObject提供程序一起使用 有许多人只是将ADsDSOObject OLEDB提供程序与ADO一起使用来查询LDAP。这就解决了必须找到正确的LDAP路径的问题—您不必知道用户的LDAP路径

String sql = 
    'SELECT userAccountControl FROM "LDAP://DC=contoso,DC=test"
    'WHERE objectClass="user" 
    'and sAMAccountName = "iboyd"';

String connectionString = "Provider=ADsDSOObject;Password=Tr0ub4dor&3;
      User ID=iboyd;Encrypt Password=True;Mode=Read;
      Bind Flags=0;ADSI Flag=-2147483648";

Connection conn = new ADODB.Connection();
conn.ConnectionString = connectionString;
conn.Open();
IRecordset rs = conn.Execute(sql);
这是可行的;它解决了不知道用户LDAP路径的问题。但它不能解决如果你没有查询广告的权限,那么它就会失败的问题

另外还有一个问题,即它在查询Active Directory时应该验证凭据

C#
PrincipalContext
不会遇到此问题

方法6:只需使用PrincipalContext.ValidateCredentials即可 .NET 3.5类
PrincipalContext
提供了一个仅允许您在知道以下信息的情况下验证凭据的功能:

  • 用户名
  • 密码
  • 域名
您不需要知道AD服务器的名称或IP。您不需要构造任何LDAP路径。最重要的是,查询Active Directory不需要权限,它可以正常工作

我试着使用ILSpy深入挖掘源代码,但很快就得到了harry:

ValidateCredentials
   CredentialValidator.Validate
      BindLdap
         new LdapDirectoryIdentifier
         new LdapConnection
         ldapConnection.SessionOptions.FastConcurrentBind();
            lockedLdapBind
                Bind
周围有一大堆大概很重要的代码。依赖项注入有很多起伏,函数太少——这些都是您在过于复杂的代码结构中遇到的正常困难。复杂性与编程的SSPI相当。没有人理解SSPI代码,我已经编写了调用它的代码

注意:此问题不询问如何验证本地凭据,而不是验证本地凭据。它也没有问如何两者兼顾。在本例中,我只是问如何做在.NET世界中已经可用但在本机世界中可用的事情

不幸的是:

System.Security.DirectoryServices.AccountManagement.PrincipalContext
未通过COM可调用包装器公开:

现在我已经花了两个半小时
ValidateCredentials
   CredentialValidator.Validate
      BindLdap
         new LdapDirectoryIdentifier
         new LdapConnection
         ldapConnection.SessionOptions.FastConcurrentBind();
            lockedLdapBind
                Bind
System.Security.DirectoryServices.AccountManagement.PrincipalContext