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风格的ldap
API.但是试图从ILSpy反向设计代码并没有成功.尽管引用了源,但Microsoft仍然保留了.NET Framework类库源代码的大部分内容.
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
不会遇到这个问题.
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
不会遇到这个问题.
有人可能建议使用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
不会遇到这个问题.
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
不会遇到这个问题.
有许多人只是将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
不会遇到这个问题.
.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可调用包装器公开:
现在,我花了两个半小时打字这个问题:是时候回家了.让我们看看我现在和明天早上之间是否关闭.
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 进行登录。