Win32的:如何验证对Active Directory凭据?(Win32: How to validate credentials against Active Directory?)

289 IT屋

It has been asked, and answered for .NET, but now it's time to get an answer for native Win32 code:

How do i validate a Windows username and password?

i asked this question before for managed code. Now it's time for the native solution.


It needs to be pointed the pitfalls with some of the more commonly proposed solutions:

Invalid Method 1. Query Active Directory with Impersonation

A lot of people suggest querying the Active Directory for something. If an exception is thrown, then you know the credentials are not valid - as is suggested in this stackoverflow question.

There are some serious drawbacks to this approach however:

  • You are not only authenticating a domain account, but you are also doing an implicit authorization check. That is, you are reading properties from the AD using an impersonation token. What if the otherwise valid account has no rights to read from the AD? By default all users have read access, but domain policies can be set to disable access permissions for restricted accounts (and or groups).

  • Binding against the AD has a serious overhead, the AD schema cache has to be loaded at the client (ADSI cache in the ADSI provider used by DirectoryServices). This is both network, and AD server, resource consuming - and is too expensive for a simple operation like authenticating a user account.

  • You're relying on an exception failure for a non-exceptional case, and assuming that means invalid username and password. Other problems (e.g. network failure, AD connectivity failure, memory allocation error, etc) are then mis-intrepreted as authentication failure.

The use of the DirectoryEntry class is .NET is an example of an incorrect way to verify credentials:

Invalid Method 1a - .NET

DirectoryEntry entry = new DirectoryEntry("persuis", "iboyd", "Tr0ub4dor&3");
object nativeObject = entry.NativeObject;

Invalid Method 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;
                }
            }
        }
    }

As well as querying Active Directory through an ADO connection:

Invalid Method 1c - Native Query

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'

These both fail even when your credentials are valid, but you do not have permission to view your directory entry:

enter image description here

Invalid Method 2. LogonUser Win32 API

Others have suggested using the LogonUser() API function. This sounds nice, but unfortunatly the calling user sometimes needs a permission ususally only given to the operating system itself:

The process calling LogonUser requires the SE_TCB_NAME privilege. If the calling process does not have this privilege, LogonUser fails and GetLastError returns ERROR_PRIVILEGE_NOT_HELD.

In some cases, the process that calls LogonUser must also have the SE_CHANGE_NOTIFY_NAME privilege enabled; otherwise, LogonUser fails and GetLastError returns ERROR_ACCESS_DENIED. This privilege is not required for the local system account or accounts that are members of the administrators group. By default, SE_CHANGE_NOTIFY_NAME is enabled for all users, but some administrators may disable it for everyone.

Handing out the "Act as a part of the operating system" privelage is not something you want to do willy-nilly - as Microsoft points out in a knowledge base article:

...the process that is calling LogonUser must have the SE_TCB_NAME privilege (in User Manager, this is the "Act as part of the Operating System" right). The SE_TCB_NAME privilege is very powerful and should not be granted to any arbitrary user just so that they can run an application that needs to validate credentials.

Additionally, a call to LogonUser() will fail if a blank password is specified.


Valid .NET 3.5 Method - PrincipalContext

There is a validation method, only available in .NET 3.5 and newer, that allows authentication by a user without performing an authorization check:

// 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")
}

Unfortunately this code is only available in .NET 3.5 and later.

It's time to find the native equivalent.

解决方案

Here is Microsoft's recommendation.

As for the other answers, I'm not really sure why you're shooting them down. You are complaining about (relatively edge case) failures while trying to validate credentials, but if you are going to actually do something with those credentials then that operation is just going to fail anyway. If you are not going to actually do something with those credentials, then why do you need to validate them in the first place? It seems like a somewhat contrived situation, but obviously I don't know what you're trying to accomplish.

这已<一个href="http://stackoverflow.com/questions/290548/c-validate-a-username-and-password-against-active-directory">asked,和回答了.NET 的,但现在是时候来获取本地答案Win32的code:

我如何验证的Windows用户名和密码?

之前问这个问题的管理code 。现在是时候了原生解决方案。


有需要指出的缺陷与一些比较常用提出的解决方案:

无效的方法1.查询活动与模拟目录

很多人<一个href="http://stackoverflow.com/questions/290548/c-validate-a-username-and-password-against-active-directory/290580#290580">suggest查询活动目录的东西。如果抛出一个异常,那么你知道凭据无效 - 如建议在<一个href="http://stackoverflow.com/questions/290548/c-validate-a-username-and-password-against-active-directory/290580#290580">this计算器问题。

有<一href="http://www.pcreview.co.uk/forums/fyi-easy-way-validate-ad-credentials-win2k-using-c-t1374121.html">some然而,严重的缺点,以这种方式的:

  
      
  • 您不仅验证域帐户,但你也做一个隐含的授权检查。也就是说,你从AD使用模拟令牌读取性能。如果什么其他有效帐户无权从AD读?默认情况下所有用户都具有读取权限,但域策略可以设置为禁止访问权限受限帐户(和或组)。

  •   
  • 绑定对AD有严重的开销,AD架构缓存有在客户端(在使用的DirectoryServices的ADSI提供ADSI缓存)被加载。这是两个网络,和AD服务器,消耗资源 - 而且是一个简单的操作就像验证用户帐户太贵

  •   
  • 您正在依靠一个非特殊情况下的异常故障,并假设这意味着无效的用户名和密码。其他问题(如网络故障,AD连接故障,内存分配错误等),然后PTED为验证失败错误intre $ P $。

  •   

使用的<一个href="http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx"><$c$c>DirectoryEntry类是.NET是一个不正确的方法来验证凭据的例子:

无效的方法1A - .NET

 的DirectoryEntry条目=新的DirectoryEntry("persuis","iboyd","Tr0ub4dor及3");
反对nativeObject = entry.NativeObject;
 

无效的方法1B - .NET#2

 公共静态布尔CheckADUserCredentials(字符串帐户名,密码字符串,字符串域)
{
    布尔结果;

    使用(的DirectoryEntry条目=新的DirectoryEntry("LDAP://"+域名,帐户名,密码))
    {
        使用(DirectorySearcher从搜索=新DirectorySearcher从(输入))
        {
            字符串过滤器=的String.Format("(及(objectCategory属性=用户)(sAMAccountName赋= {0}))",帐户名);
            sea​​rcher.Filter =过滤器;
            尝试
            {
                信息搜索结果adsSearchResult = searcher.FindOne();
                结果=真;
            }
            赶上(DirectoryServicesCOMException前)
            {
                const int的SEC_E_LOGON_DENIED = -2146893044; // 0x8009030C;
                如果(ex.ExtendedError == SEC_E_LOGON_DENIED)
                {
                    //无法验证。
                    结果= FALSE;
                }
                其他
                {
                    扔;
                }
            }
        }
    }
 

除了通过ADO连接查询Active Directory:

无效的方法1C - 原生查询

 的connectionString ="供应商= ADsDSOObject;
       用户ID = iboyd;密码= Tr0ub4dor及3;
       加密密码= TRUE;模式=阅读;
       绑定标志= 0; ADSI标志= -2147483648';"

选择userAccountControl的
从"LDAP:// persuis / DC =计算器,DC = COM"
WHERE对象类='用户'而sAMAccountName ='iboyd"
 

这些均不能当你的凭据是有效,但您没有权限查看您的目录条目:

输入图像的描述在这里

无效的方法2的LogonUser Win32 API的

其他建议使用的 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是不是你想要做无可奈何的东西 - 微软指出,在一个的knowledge相应的文章

  

...正在调用的过程   LogonUser的必须有SE_TCB_NAME   特权(在用户管理器,这是   在"作为操作系统的一部分   系统的"右)的SE_TCB_NAME   特权是非常强大的,   不应该被授予任意用户只需使他们能   运行应用程序需要   验证凭据。

此外,对LogonUser的调用()如果指定空白密码失败。


有效的.NET 3.5的方法 - PrincipalContext

有一种验证方法,只能在.NET 3.5和更新可用时,可以通过验证用户不执行授权检查:

  //创建一个"主要方面" - 如:您的域(可能是机器,太)
使用(PrincipalContext PC =新PrincipalContext(ContextType.Domain,"stackoverflow.com"))
{
    //验证凭据
    布尔的isValid = pc.ValidateCredentials("iboyd","Tr0ub4dor和3")
}
 

不幸的是这code是只适用于.NET 3.5和更高版本。

现在是时候找到的本机的等价。

解决方案

下面是微软的建议

至于其他的答案,我真的不知道为什么你拍摄下来。你都在抱怨(相对边缘的情况下)故障而试图验证证书,但如果你要真正做一些与这些证书,那么该操作只是去反正失败。如果你不打算做一些事情与这些凭据,那么你为什么需要验证它们摆在首位?这似乎是一个有点做作的情况,但很明显,我不知道你要完成的任务。

本文地址:IT屋 » Win32的:如何验证对Active Directory凭据?