Ian*_*oyd 16 windows security authentication winapi active-directory
有人问过,并回答过.NET,但是现在是时候得到原生Win32代码的答案了:
如何验证Windows用户名和密码?
我之前问过托管代码这个问题.现在是原生解决方案的时候了.
需要指出一些更常见的解决方案的陷阱:
很多人建议查询Active Directory.如果抛出异常,那么您知道凭据无效 - 正如此stackoverflow问题中所建议的那样.
您不仅要对域帐户进行身份验证,还要进行隐式授权检查.也就是说,您正在使用模拟令牌从AD中读取属性.如果其他有效帐户无权从AD读取,该怎么办?默认情况下,所有用户都具有读取权限,但可以将域策略设置为禁用受限帐户(和/或组)的访问权限.
绑定AD会产生严重的开销,必须在客户端加载AD架构缓存(DirectoryServices使用的ADSI提供程序中的ADSI缓存).这既是网络又是AD服务器,消耗资源 - 而且对于像验证用户帐户这样的简单操作来说太昂贵了.
您依赖于非例外情况的异常失败,并假设这意味着无效的用户名和密码.然后,其他问题(例如,网络故障,AD连接故障,内存分配错误等)被错误地表示为身份验证失败.
使用DirectoryEntry该类是.NET是验证凭据的错误方法的示例:
无效的方法1a - .NET
DirectoryEntry entry = new DirectoryEntry("persuis", "iboyd", "Tr0ub4dor&3");
object nativeObject = entry.NativeObject;
Run Code Online (Sandbox Code Playgroud)
无效的方法1b - .NET#2
public static Boolean CheckADUserCredentials(String accountName, String password, String domain)
{
Boolean result;
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain, accountName, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
String filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
searcher.Filter = filter;
try
{
SearchResult adsSearchResult = searcher.FindOne();
result = true;
}
catch (DirectoryServicesCOMException ex)
{
const int SEC_E_LOGON_DENIED = -2146893044; //0x8009030C;
if (ex.ExtendedError == SEC_E_LOGON_DENIED)
{
// Failed to authenticate.
result = false;
}
else
{
throw;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
以及通过ADO连接查询Active Directory:
无效的方法1c - 本机查询
connectionString = "Provider=ADsDSOObject;
User ID=iboyd;Password=Tr0ub4dor&3;
Encrypt Password=True;Mode=Read;
Bind Flags=0;ADSI Flag=-2147483648';"
SELECT userAccountControl
FROM 'LDAP://persuis/DC=stackoverflow,DC=com'
WHERE objectClass='user' and sAMAccountName = 'iboyd'
Run Code Online (Sandbox Code Playgroud)
即使您的凭据有效,这些都会失败,但您无权查看目录条目:

其他人建议使用LogonUser() API函数.这听起来不错,但不幸的是,调用用户有时只需要一个权限,通常只给操作系统本身:
调用LogonUser的进程需要SE_TCB_NAME权限.如果调用进程没有此权限,LogonUser将失败,GetLastError将返回ERROR_PRIVILEGE_NOT_HELD.
在某些情况下,调用LogonUser的进程还必须启用SE_CHANGE_NOTIFY_NAME权限; 否则,LogonUser失败,GetLastError返回ERROR_ACCESS_DENIED.作为管理员组成员的本地系统帐户或帐户不需要此权限.默认情况下,为所有用户启用SE_CHANGE_NOTIFY_NAME,但某些管理员可能会为所有用户禁用它.
省高院" 法案作为操作系统的一部分 " privelage是不是你想要做无可奈何的东西-作为微软在一份指出知识库文章:
...调用LogonUser的进程必须具有SE_TCB_NAME权限(在用户管理器中,这是" 作为操作系统的一部分 "权限).SE_TCB_NAME权限非常强大, 不应授予任何任意用户,以便他们可以运行需要验证凭据的应用程序.
此外,如果指定了空白密码,则对LogonUser()的调用将失败.
PrincipalContext有一种验证方法,仅适用于.NET 3.5及更高版本,允许用户进行身份验证而无需执行授权检查:
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "stackoverflow.com"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("iboyd", "Tr0ub4dor&3")
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,此代码仅在.NET 3.5及更高版本中可用.
是时候找到原生的等价物了.