C# 如何验证域凭据?

C# 如何验证域凭据?,c#,windows,security,authentication,C#,Windows,Security,Authentication,我想根据域控制器验证一组凭据。e、 g: Username: STACKOVERFLOW\joel Password: splotchy 方法1。使用模拟查询Active Directory 很多人建议在Active Directory中查询一些东西。如果引发异常,则您知道凭据无效-如中所建议的 然而,有一些严重的问题: 您不仅对域帐户进行身份验证,而且还进行隐式授权检查。也就是说,您正在使用模拟令牌从广告中读取属性。如果有效的帐户没有读取广告的权限怎么办?默认情况下,所有用户都具有读取权限,

我想根据域控制器验证一组凭据。e、 g:

Username: STACKOVERFLOW\joel
Password: splotchy
方法1。使用模拟查询Active Directory 很多人建议在Active Directory中查询一些东西。如果引发异常,则您知道凭据无效-如中所建议的

然而,有一些严重的问题:

  • 您不仅对域帐户进行身份验证,而且还进行隐式授权检查。也就是说,您正在使用模拟令牌从广告中读取属性。如果有效的帐户没有读取广告的权限怎么办?默认情况下,所有用户都具有读取权限,但可以将域策略设置为禁用受限帐户(和或组)的访问权限

  • 针对AD的绑定具有严重的开销,必须在客户端加载AD架构缓存(DirectoryServices使用的ADSI提供程序中的ADSI缓存)。这既是网络,又是广告服务器,消耗资源——对于像验证用户帐户这样的简单操作来说,成本太高

  • 对于非异常情况,您依赖于异常失败,并假设这意味着用户名和密码无效。其他问题(如网络故障、AD连接故障、内存分配错误等)则被错误地称为身份验证故障

  • 方法2。登录用户Win32 API 建议使用API函数。这听起来不错,但不幸的是,调用用户有时需要一个通常只授予操作系统本身的权限:

    调用LogonUser的进程需要 SE_TCB_NAME特权。如果 调用进程没有此选项 特权、登录用户失败和 GetLastError返回 错误\u权限\u未被保留

    在某些方面 案例,调用 LogonUser还必须具有 更改通知姓名权限 启用;否则,LogonUser将失败 和GetLastError返回 错误\u访问被拒绝。这项特权是 本地系统不需要 一个或多个为成员的帐户 管理员组的。通过 默认情况下,SE_CHANGE_NOTIFY_NAME为 为所有用户启用,但有些用户 管理员可以将其禁用 各位

    发放“作为操作系统的一部分”特权并不是你想随意做的事情——正如微软在一份声明中指出的:

    …正在调用的进程 LogonUser必须具有SE_TCB_名称 特权(在用户管理器中,这是 “作为操作系统的一部分” 系统(右)。SE_TCB_名称 特权是非常强大的 不应授予任何任意用户,以便他们能够 运行需要 验证凭据

    此外,如果指定了空密码,则调用
    LogonUser()
    将失败


    验证一组域凭据的正确方法是什么


    我碰巧是从托管代码中调用的,但这是一个一般的Windows问题。可以假设客户已经安装了.NET Framework 2.0。

    C#在.NET 3.5中使用

    这将针对当前域进行验证。查看参数化PrincipalContext构造函数以获取其他选项

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.DirectoryServices.AccountManagement;
    
    class WindowsCred
    {
        private const string SPLIT_1 = "\\";
    
        public static bool ValidateW(string UserName, string Password)
        {
            bool valid = false;
            string Domain = "";
    
            if (UserName.IndexOf("\\") != -1)
            {
                string[] arrT = UserName.Split(SPLIT_1[0]);
                Domain = arrT[0];
                UserName = arrT[1];
            }
    
            if (Domain.Length == 0)
            {
                Domain = System.Environment.MachineName;
            }
    
            using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Domain)) 
            {
                valid = context.ValidateCredentials(UserName, Password);
            }
    
            return valid;
        }
    }
    
    卡西夫·穆斯塔克
    加拿大渥太华

    我正在使用以下代码验证凭据。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Security;
    using System.DirectoryServices.AccountManagement;
    
    public struct Credentials
    {
        public string Username;
        public string Password;
    }
    
    public class Domain_Authentication
    {
        public Credentials Credentials;
        public string Domain;
    
        public Domain_Authentication(string Username, string Password, string SDomain)
        {
            Credentials.Username = Username;
            Credentials.Password = Password;
            Domain = SDomain;
        }
    
        public bool IsValid()
        {
            using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
            {
                // validate the credentials
                return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
            }
        }
    }
    
    下面显示的方法将确认凭据是否正确,如果不正确,则确认密码是否已过期或需要更改

    我一直在寻找这样的东西很久了。。。所以我希望这能帮助别人

    using System;
    using System.DirectoryServices;
    using System.DirectoryServices.AccountManagement;
    using System.Runtime.InteropServices;
    
    namespace User
    {
        public static class UserValidation
        {
            [DllImport("advapi32.dll", SetLastError = true)]
            static extern bool LogonUser(string principal, string authority, string password, LogonTypes logonType, LogonProviders logonProvider, out IntPtr token);
            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool CloseHandle(IntPtr handle);
            enum LogonProviders : uint
            {
                Default = 0, // default for platform (use this!)
                WinNT35,     // sends smoke signals to authority
                WinNT40,     // uses NTLM
                WinNT50      // negotiates Kerb or NTLM
            }
            enum LogonTypes : uint
            {
                Interactive = 2,
                Network = 3,
                Batch = 4,
                Service = 5,
                Unlock = 7,
                NetworkCleartext = 8,
                NewCredentials = 9
            }
            public  const int ERROR_PASSWORD_MUST_CHANGE = 1907;
            public  const int ERROR_LOGON_FAILURE = 1326;
            public  const int ERROR_ACCOUNT_RESTRICTION = 1327;
            public  const int ERROR_ACCOUNT_DISABLED = 1331;
            public  const int ERROR_INVALID_LOGON_HOURS = 1328;
            public  const int ERROR_NO_LOGON_SERVERS = 1311;
            public  const int ERROR_INVALID_WORKSTATION = 1329;
            public  const int ERROR_ACCOUNT_LOCKED_OUT = 1909;      //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!!
            public  const int ERROR_ACCOUNT_EXPIRED = 1793;
            public  const int ERROR_PASSWORD_EXPIRED = 1330;
    
            public static int CheckUserLogon(string username, string password, string domain_fqdn)
            {
                int errorCode = 0;
                using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD"))
                {
                    if (!pc.ValidateCredentials(username, password))
                    {
                        IntPtr token = new IntPtr();
                        try
                        {
                            if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token))
                            {
                                errorCode = Marshal.GetLastWin32Error();
                            }
                        }
                        catch (Exception)
                        {
                            throw;
                        }
                        finally
                        {
                            CloseHandle(token);
                        }
                    }
                }
                return errorCode;
            }
        }
    

    以下是如何确定本地用户:

        public bool IsLocalUser()
        {
            return windowsIdentity.AuthenticationType == "NTLM";
        }
    

    伊恩·博伊德编辑 你不应该再使用NTLM了。它太旧了,太糟糕了,以至于微软的应用程序验证程序(用于捕获常见的编程错误)如果检测到您使用NTLM,就会发出警告

    下面是Application Verifier文档中的一章,介绍了如果有人错误地使用NTLM,他们为什么要进行测试:

    为什么需要NTLM插件 NTLM是一种过时的身份验证协议,存在以下缺陷: 可能会危及应用程序和操作系统的安全性 系统。最重要的缺点是缺少服务器 身份验证,允许攻击者诱骗用户 连接到伪造的服务器。作为缺少服务器的必然结果 身份验证时,使用NTLM的应用程序也可能容易受到攻击 被称为“反射”攻击的攻击类型。后者允许 攻击者将用户的身份验证对话劫持到 合法服务器,并使用它向 用户的计算机。NTLM的漏洞及其利用方法 是增加安全领域研究活动的目标 社区

    尽管Kerberos已经存在很多年了,但是许多应用程序 仍然编写为仅使用NTLM。这不必要地降低了成本 应用程序的安全性。但是,Kerberos不能完全取代NTLM 场景–主要是客户机需要对其进行身份验证的场景 未加入域(可能是家庭网络)的系统 其中最常见的一种)。协商安全包允许 尽可能使用Kerberos的向后兼容折衷方案 只有在没有其他选项时才恢复为NTLM。交换码 使用协商而不是NTLM将显著增加 为我们的客户提供安全保护,同时引入很少或没有应用程序 相容性。谈判本身并不是一颗灵丹妙药——确实如此 攻击者可以强制降级到NTLM,但这些都是 显然更难利用。然而,一个直接的问题是: 改进之处在于编写的应用程序能够正确使用协商 自动对NTLM反射攻击免疫

    最后,我要提醒大家不要在将来使用NTLM 在Windows版本中,可以禁用
        public bool IsLocalUser()
        {
            return windowsIdentity.AuthenticationType == "NTLM";
        }
    
    APIs(parameter) Used by Application    Incorrect Value  Correct Value  
    =====================================  ===============  ========================
    AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”