针对该域验证一组用户域凭据(用户名,密码,域)的本机方法是什么?
换句话说,我正在寻找原生的等价物:
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类库源代码的大部分内容。
LogonUser
我无法使用LogonUser
。 LogonUser仅在您的计算机 您正在验证的域(例如contoso
)或您的域信任您所在的域时才有效证实。换句话说,如果 contoso.test 域中存在域控制器,则:
LogonUser("iboyd", "contoso.test", "Tr0ub4dor&3",
LOGON32_LOGON_NETWORK, "Negotiate", ref token);
将失败并显示错误:
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
如果您的计算机已加入您正在验证凭据的域,则此代码非常有用。但是,只要您尝试从外部域验证一组域凭证:它就会失败。
C#
PrincipalContext
并未遇到此问题。
有些人可能会建议使用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
并未遇到此问题。
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
并未遇到此问题。
有许多人只是将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
并未遇到此问题。
.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可调用包装器公开:
现在,我已经花了两个半小时输入这个问题:是时候回家了。让我们看看从现在到明天早上我是否会被关闭。