什么是PrincipalContext.ValidateCredentials的本机等价物?

时间:2015-03-17 21:38:13

标签: 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 PrincipalContext(ContextType.Domain, domain))
   {
      // validate the credentials
      return pc.ValidateCredentials(username, password)
   }
}

ValidateCredentials("iboyd", "Tr0ub4dor&3", "contoso");

有没有人问过这个问题并回答死亡?

没有!这个问题很多。我这三次。但是你挖掘的越多,你就越能意识到接受的答案是不正确的。

Microsoft设法在.NET中使用.NET 3.5中添加的 PrincipalContext 类来解决它。并且 PrincipalContext 并不神奇。它下面使用扁平的C风格的ldap API。但是试图从ILSpy反向设计代码并没有成功。尽管引用了源,但Microsoft仍然保留了.NET Framework类库源代码的大部分内容。

你有什么尝试?

方法1 只需使用LogonUser

我无法使用LogonUser。 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-code 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

如果您的计算机已加入您正在验证凭据的域,则此代码非常有用。但是,只要您尝试从外部域验证一组域凭证:它就会失败。

  

C#PrincipalContext并未遇到此问题。

方法3 只需使用LDAP' s AdsGetObject

有些人可能会建议使用AdsGetObject

AdsGetObject("LDAP://CN=iboyd,DC=contoso,DC=test");

这是一个红鲱鱼,因为AdsGetObject无法通过用户名/密码:

HRESULT ADsGetObject(
  _In_   LPCWSTR lpszPathName,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);

相反,您只是询问用户。

也许你的意思是AdsOpenObject

HRESULT ADsOpenObject(
  _In_   LPCWSTR lpszPathName,
  _In_   LPCWSTR lpszUserName,
  _In_   LPCWSTR lpszPassword,
  _In_   DWORD dwReserved,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);

可以指定要连接的凭据。

  

C#PrincipalContext并未遇到此问题。

方法4 只需使用AdsOpenObject

有些人可能会建议使用AdsOpenObject

String path = "LDAP://CN=iboyd,DC=contoso,DC=test"
AdsOpenObject(path, "iboyd", "Tr0ub4dor&3", 0, IADs, ref ads);

撇开我构造的路径无效的事实,撇开以下事实:当您只知道时,无法构建有效的LDAP路径:

  • {用户名}
  • {密码}
  • {域}

那是因为

  

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

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

尽管存在LDAP路径难题,但根本问题在于尝试查询 LDAP。那是错的。

我们希望验证用户的AD凭据。

每当我们尝试查询 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路径的问题。但它没有解决问题,如果你没有权限查询AD,那么它就会失败。

此外还有一个问题是它在查询Active Directory时应该验证凭据。

  

C#PrincipalContext并未遇到此问题。

方法6 只需使用PrincipalContext.ValidateCredentials

.NET 3.5类PrincipalContext允许您仅知道验证凭据:

  • 用户名
  • 密码
  • 域名

您不需要知道AD服务器的名称或IP。您不需要构建任何LDAP路径。最重要的是,您不需要查询Active Directory的权限 - 它只是有效。

我尝试使用ILSpy挖掘源代码,但速度很快:

ValidateCredentials
   CredentialValidator.Validate
      BindLdap
         new LdapDirectoryIdentifier
         new LdapConnection
         ldapConnection.SessionOptions.FastConcurrentBind();
            lockedLdapBind
                Bind

围绕着很多大概重要的代码。有很多上升和下降,依赖注入,功能太少 - 所有正常的困难,你得到过于复杂的代码结构。复杂性与编程SSPI相同。没有人理解SSPI代码,我已经写了代码来调用它!

注意:此问题并不询问如何验证本地凭据,而不是本地凭据。它也不会问如何做到这两点。在这种情况下,我只是简单地询问如何在.NET世界中,但在本地世界中已经可以使用的东西。

不幸的是:

System.Security.DirectoryServices.AccountManagement.PrincipalContext

没有通过COM可调用包装器公开:

enter image description here

现在,我已经花了两个半小时输入这个问题:是时候回家了。让我们看看从现在到明天早上我是否会被关闭。

0 个答案:

没有答案