我想针对域控制器验证一组凭据。 e.g:
Username: STACKOVERFLOW\joel
Password: splotchy
很多人建议查询Active Directory。如果抛出异常,则表示凭据无效 - 如this stackoverflow question中所述。
然而,有一些严重的drawbacks to this approach:
您不仅要对域帐户进行身份验证,还要进行隐式授权检查。也就是说,您正在使用模拟令牌从AD中读取属性。如果其他有效帐户无权从AD读取,该怎么办?默认情况下,所有用户都具有读取权限,但可以将域策略设置为禁用受限制帐户(和/或组)的访问权限。
绑定AD会产生严重的开销,必须在客户端加载AD架构缓存(DirectoryServices使用的ADSI提供程序中的ADSI缓存)。这是网络和AD服务器的资源消耗 - 并且对于像验证用户帐户这样的简单操作来说太昂贵了。
您依赖于非例外情况的异常失败,并假设这意味着无效的用户名和密码。其他问题(例如网络故障,AD连接故障,内存分配错误等)随后被误认为是身份验证失败。
Others建议使用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是 为所有用户启用,但有些用户启用 管理员可以禁用它 每个人。
将“法案作为操作系统的一部分”特权发送出去并不是你想要做的事情 - 正如微软在knowledge base article中所指出的那样:
......正在呼叫的过程 LogonUser必须拥有SE_TCB_NAME 特权(在用户管理器中,这是 “作为运营的一部分 系统“正确”.SE_TCB_NAME 特权是非常强大的 不应该被授予任何任意用户,只是为了他们可以 运行需要的应用程序 验证凭证。
此外,如果指定了空白密码,则对LogonUser()
的调用将失败。
验证一组域凭据的正确方法是什么?
我发生从托管代码调用,但这是一个常见的Windows问题。可以假设客户已安装.NET Framework 2.0。
答案 0 :(得分:118)
.NET 3.5中的C#使用System.DirectoryServices.AccountManagement。
bool valid = false;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
valid = context.ValidateCredentials( username, password );
}
这将验证当前域。查看参数化的PrincipalContext构造函数以获取其他选项。
答案 1 :(得分:17)
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);
}
}
}
答案 2 :(得分:5)
我使用以下代码验证凭据。 下面显示的方法将确认凭据是否正确,如果密码已过期或需要更改,则会确认。
我多年来一直在寻找这样的东西......所以我希望这可以帮助别人!
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;
}
}
答案 3 :(得分:1)
以下是如何确定本地用户:
public bool IsLocalUser()
{
return windowsIdentity.AuthenticationType == "NTLM";
}
你根本不应该使用NTLM。微软的应用程序验证程序(用于捕获常见的编程错误)如果它检测到你使用NTLM就会发出警告,这是如此古老,如此糟糕。
以下是Application Verifier文档中的一章,说明如果有人错误地使用NTLM,他们为什么要进行测试:
为什么需要NTLM插件
NTLM是一种过时的身份验证协议,存在漏洞 可能会危及应用程序和操作的安全性 系统。最重要的缺点是缺少服务器 身份验证,可能允许攻击者欺骗用户 连接到欺骗性服务器。作为缺少服务器的必然结果 身份验证,使用NTLM的应用程序也可能容易受到攻击 被称为“反射”攻击的攻击类型。后者允许 攻击者将用户的身份验证会话劫持到 合法服务器并使用它来验证攻击者 用户的电脑。 NTLM的漏洞和利用它们的方式 是增加安全研究活动的目标 社区。 P>
虽然Kerberos已经有很多年的应用程序可用 仍然只能使用NTLM编写。这不必要地减少了 应用程序的安全性但是,Kerberos无法取代所有的NTLM 场景 - 主要是客户端需要进行身份验证的场景 未加入域的系统(可能是家庭网络) 其中最常见的)。 Negotiate安全包允许 尽可能使用Kerberos的向后兼容的折衷方案 并且只有在没有其他选项时才会恢复为NTLM。切换代码 使用Negotiate而不是NTLM会显着增加 在引入很少或没有应用程序时为我们的客户提供安 兼容性。谈判本身并不是一颗银弹 - 那里 是攻击者可以强制降级到NTLM的情况,但这些是 更难以利用。但是,一个立即 改进是编写的应用程序正确使用Negotiate 自动免受NTLM反射攻击。
最后谨慎使用NTLM:将来 Windows的版本可以禁用NTLM的使用 操作系统。如果应用程序对NTLM有很强的依赖性 当NTLM被禁用时,他们将无法进行身份验证。
插件如何工作
Verifier插件检测到以下错误:
在对AcquireCredentialsHandle(或更高级别的包装API)的调用中直接指定了NTLM包。
对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”
答案 4 :(得分:-1)
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 加拿大渥太华