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

Ian*_*oyd 5 winapi credentials active-directory

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

换句话说,我正在寻找本机相当于:

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");
Run Code Online (Sandbox Code Playgroud)

难道这没有被问及并回答死亡?

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

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

你有什么尝试?

方法1:使用LogonUser

我不能用LogonUser.LogonUser的工作,只有当你的机器要么你验证域(例如contoso)或域信任你验证域.换句话说,如果contoso.test域有域控制器,那么:

LogonUser("iboyd", "contoso.test", "Tr0ub4dor&3", 
      LOGON32_LOGON_NETWORK, "Negotiate", ref token);
Run Code Online (Sandbox Code Playgroud)

将失败并出现错误:

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
Run Code Online (Sandbox Code Playgroud)

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

C#PrincipalContext不会遇到这个问题.

方法3:只需使用LDAP的AdsGetObject

有人可能建议使用AdsGetObject.

AdsGetObject("LDAP://CN=iboyd,DC=contoso,DC=test");
Run Code Online (Sandbox Code Playgroud)

这是一个红鲱鱼,因为AdsGetObject支持无法传递用户名/密码:

HRESULT ADsGetObject(
  _In_   LPCWSTR lpszPathName,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);
Run Code Online (Sandbox Code Playgroud)

相反,您只是询问用户.

也许你的意思是AdsOpenObject:

HRESULT ADsOpenObject(
  _In_   LPCWSTR lpszPathName,
  _In_   LPCWSTR lpszUserName,
  _In_   LPCWSTR lpszPassword,
  _In_   DWORD dwReserved,
  _In_   REFIID riid,
  _Out_  VOID **ppObject
);
Run Code Online (Sandbox Code Playgroud)

可以在其中指定要连接的凭据.

C#PrincipalContext不会遇到这个问题.

方法4:使用AdsOpenObject

有人可能建议使用AdsOpenObject:

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

撇开我构造的路径无效的事实,撇开以下事实:当您只知道时,无法构建有效的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);
Run Code Online (Sandbox Code Playgroud)

这样可行; 它解决了不知道用户的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
Run Code Online (Sandbox Code Playgroud)

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

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

不幸:

System.Security.DirectoryServices.AccountManagement.PrincipalContext
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

现在,我花了两个半小时打字这个问题:是时候回家了.让我们看看我现在和明天早上之间是否关闭.

Bon*_*lin 0

advapi.dll 中的 LogonUser 函数怎么样,例如LogonUserA

BOOL LogonUserA(
  [in]           LPCSTR  lpszUsername,
  [in, optional] LPCSTR  lpszDomain,
  [in, optional] LPCSTR  lpszPassword,
  [in]           DWORD   dwLogonType,
  [in]           DWORD   dwLogonProvider,
  [out]          PHANDLE phToken
);
Run Code Online (Sandbox Code Playgroud)
LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hToken)
Run Code Online (Sandbox Code Playgroud)

这将根据本地计算机上的 AD 进行登录。