C# 如何验证域凭据?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/326818/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 23:20:16  来源:igfitidea点击:

How to validate domain credentials?

c#windowssecurityauthentication

提问by Ian Boyd

I want to validate a set of credentials against the domain controller. e.g.:

我想针对域控制器验证一组凭据。例如:

Username: STACKOVERFLOW\joel
Password: splotchy

Method 1. Query Active Directory with Impersonation

方法 1. 使用模拟查询 Active Directory

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.

很多人建议在 Active Directory 中查询某些内容。如果抛出异常,则您知道凭据无效 - 正如此 stackoverflow question 中所建议的那样。

There are some serious drawbacks to this approachhowever:

然而,这种方法有一些严重的缺点

  1. 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).

  2. 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.

  3. 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.

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

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

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

Method 2. LogonUser Win32 API

方法二、LogonUser Win32 API

Othershave suggested using the LogonUser()API function. This sounds nice, but unfortunately the calling user sometimes needs a permission usually only given to the operating system itself:

其他人建议使用LogonUser()API 函数。这听起来不错,但不幸的是,调用用户有时需要通常只授予操作系统本身的权限:

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.

调用 LogonUser 的进程需要 SE_TCB_NAME 权限。如果调用进程没有此权限,则 LogonUser 失败并且 GetLastError 返回 ERROR_PRIVILEGE_NOT_HELD。

在某些情况下,调用 LogonUser 的进程还必须启用 SE_CHANGE_NOTIFY_NAME 权限;否则,LogonUser 失败并且 GetLastError 返回 ERROR_ACCESS_DENIED。本地系统帐户或作为管理员组成员的帐户不需要此权限。默认情况下,SE_CHANGE_NOTIFY_NAME 为所有用户启用,但某些管理员可能为每个人禁用它。

Handing out the "Act as a part of the operating system" privilege 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 applicationthat needs to validate credentials.

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

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

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



What is the proper way to authenticate a set of domain credentials?

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



I happento be calling from managed code, but this is a a general Windows question. It can be assumed that the customers have the .NET Framework 2.0 installed.

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

采纳答案by tvanfosson

C# in .NET 3.5 using System.DirectoryServices.AccountManagement.

.NET 3.5 中的 C# 使用System.DirectoryServices.AccountManagement

 bool valid = false;
 using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
 {
     valid = context.ValidateCredentials( username, password );
 }

This will validate against the current domain. Check out the parameterized PrincipalContext constructor for other options.

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

回答by tvanfosson

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

Kashif Mushtaq Ottawa, Canada

Kashif Mushtaq 加拿大渥太华

回答by kantanomo

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);
        }
    }
}

回答by Kevinrr3

I`m using the following code to validate credentials. The method shown below will confirm if the credentials are correct and if not wether the password is expired or needs change.

我正在使用以下代码来验证凭据。下面显示的方法将确认凭据是否正确,如果不正确,密码是否已过期或需要更改。

I`ve been looking for something like this for ages... So i hope this helps someone!

我一直在寻找这样的东西多年......所以我希望这对某人有所帮助!

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

回答by Alan Nicholas

Here's how to determine a local user:

以下是确定本地用户的方法:

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


Edit by Ian Boyd

伊恩·博伊德编辑

You should not use NTLM anymore at all. It is so old, and so bad, that Microsoft's Application Verifier (which is used to catch common programming mistakes) will throw a warning if it detects you using NTLM.

您根本不应再使用 NTLM。它是如此陈旧且如此糟糕,以至于 Microsoft 的应用程序验证器(用于捕获常见的编程错误)在检测到您使用 NTLM 时会发出警告。

Here's a chapter from the Application Verifier documentation about why they have a test if someone is mistakenly using NTLM:

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

Why the NTLM Plug-in is Needed

NTLM is an outdated authentication protocol with flaws that potentially compromise the security of applications and the operating system. The most important shortcoming is the lack of server authentication, which could allow an attacker to trick users into connecting to a spoofed server. As a corollary of missing server authentication, applications using NTLM can also be vulnerable to a type of attack known as a “reflection” attack. This latter allows an attacker to hiHyman a user's authentication conversation to a legitimate server and use it to authenticate the attacker to the user's computer. NTLM's vulnerabilities and ways of exploiting them are the target of increasing research activity in the security community.

Although Kerberos has been available for many years many applications are still written to use NTLM only. This needlessly reduces the security of applications. Kerberos cannot however replace NTLM in all scenarios – principally those where a client needs to authenticate to systems that are not joined to a domain (a home network perhaps being the most common of these). The Negotiate security package allows a backwards-compatible compromise that uses Kerberos whenever possible and only reverts to NTLM when there is no other option. Switching code to use Negotiate instead of NTLM will significantly increase the security for our customers while introducing few or no application compatibilities. Negotiate by itself is not a silver bullet – there are cases where an attacker can force downgrade to NTLM but these are significantly more difficult to exploit. However, one immediate improvement is that applications written to use Negotiate correctly are automatically immune to NTLM reflection attacks.

By way of a final word of caution against use of NTLM: in future versions of Windows it will be possible to disable the use of NTLM at the operating system. If applications have a hard dependency on NTLM they will simply fail to authenticate when NTLM is disabled.

How the Plug-in Works

The Verifier plug detects the following errors:

  • The NTLM package is directly specified in the call to AcquireCredentialsHandle (or higher level wrapper API).

  • The target name in the call to InitializeSecurityContext is NULL.

  • The target name in the call to InitializeSecurityContext is not a properly-formed SPN, UPN or NetBIOS-style domain name.

The latter two cases will force Negotiate to fall back to NTLM either directly (the first case) or indirectly (the domain controller will return a “principal not found” error in the second case causing Negotiate to fall back).

The plug-in also logs warnings when it detects downgrades to NTLM; for example, when an SPN is not found by the Domain Controller. These are only logged as warnings since they are often legitimate cases – for example, when authenticating to a system that is not domain-joined.

NTLM Stops

5000 – Application Has Explicitly Selected NTLM Package

Severity – Error

The application or subsystem explicitly selects NTLM instead of Negotiate in the call to AcquireCredentialsHandle. Even though it may be possible for the client and server to authenticate using Kerberos this is prevented by the explicit selection of NTLM.

How to Fix this Error

The fix for this error is to select the Negotiate package in place of NTLM. How this is done will depend on the particular Network subsystem being used by the client or server. Some examples are given below. You should consult the documentation on the particular library or API set that you are using.

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”

为什么需要 NTLM 插件

NTLM 是一种过时的身份验证协议,存在可能危及应用程序和操作系统安全的缺陷。最重要的缺点是缺乏服务器身份验证,这可能允许攻击者诱骗用户连接到欺骗服务器。作为缺少服务器身份验证的必然结果,使用 NTLM 的应用程序也容易受到一种称为“反射”攻击的攻击。后者允许攻击者劫持用户与合法服务器的身份验证对话,并使用它来对用户计算机的攻击者进行身份验证。NTLM 的漏洞和利用它们的方法是安全社区中增加研究活动的目标。

尽管 Kerberos 已经可用多年,但许多应用程序仍然只使用 NTLM 编写。这不必要地降低了应用程序的安全性。然而,Kerberos 不能在所有情况下取代 NTLM——主要是那些客户端需要对未加入域的系统进行身份验证的情况(家庭网络可能是其中最常见的)。Negotiate 安全包允许向后兼容的妥协,只要有可能就使用 Kerberos,并且只有在没有其他选择时才恢复到 NTLM。将代码切换为使用 Negotiate 而不是 NTLM 将显着提高我们客户的安全性,同时引入很少或没有应用程序兼容性。协商本身并不是灵丹妙药——在某些情况下,攻击者可以强制降级到 NTLM,但这些情况更难以利用。然而,一项直接的改进是,为正确使用 Negotiate 而编写的应用程序会自动免疫 NTLM 反射攻击。

最后提醒一下不要使用 NTLM:在 Windows 的未来版本中,可以在操作系统中禁用 NTLM。如果应用程序对 NTLM 有严格的依赖性,当 NTLM 被禁用时,它们将无法进行身份验证。

插件的工作原理

Verifier 插件检测到以下错误:

  • NTLM 包在对 AcquireCredentialsHandle(或更高级别的包装器 API)的调用中直接指定。

  • InitializeSecurityContext 调用中的目标名称为 NULL。

  • InitializeSecurityContext 调用中的目标名称不是格式正确的 SPN、UPN 或 NetBIOS 样式的域名。

后两种情况将强制 Negotiate 直接(第一种情况)或间接回退到 NTLM(域控制器将在第二种情况下返回“找不到主体”错误,导致 Negotiate 回退)。

该插件还会在检测到降级到 NTLM 时记录警告;例如,当域控制器未找到 SPN 时。这些仅记录为警告,因为它们通常是合法的情况 - 例如,在对未加入域的系统进行身份验证时。

NTLM 停止

5000 – 应用程序明确选择了 NTLM 包

严重性 - 错误

应用程序或子系统在对 AcquireCredentialsHandle 的调用中显式选择 NTLM 而不是 Negotiate。尽管客户端和服务器可以使用 Kerberos 进行身份验证,但显式选择 NTLM 会阻止这种情况。

如何修复此错误

此错误的修复方法是选择 Negotiate 程序包代替 NTLM。如何做到这一点取决于客户端或服务器使用的特定网络子系统。下面给出了一些例子。您应该查阅有关您正在使用的特定库或 API 集的文档。

APIs(parameter) Used by Application    Incorrect Value  Correct Value  
=====================================  ===============  ========================
AcquireCredentialsHandle (pszPackage)  “NTLM”           NEGOSSP_NAME “Negotiate”