C# 与SAM一起使用System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials时出现奇怪错误

C# 与SAM一起使用System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials时出现奇怪错误,c#,.net-3.5,C#,.net 3.5,我正在使用IIS 6.0托管一个WCF Web服务。我的应用程序池在本地管理员帐户下运行,我已定义了其他本地用户以访问Web服务。我编写了以下代码来验证用户: //Any public static (Shared in Visual Basic) members of this type are thread safe public static PrincipalContext vpc; //static initializer static UserManagement() {

我正在使用IIS 6.0托管一个WCF Web服务。我的应用程序池在本地管理员帐户下运行,我已定义了其他本地用户以访问Web服务。我编写了以下代码来验证用户:


//Any public static (Shared in Visual Basic) members of this type are thread safe
public static PrincipalContext vpc;

//static initializer
static UserManagement()
{
    vpc = new PrincipalContext(ContextType.Machine);
}

//Determines whether the given credentials are for a valid Administrator
public static bool validateAdminCredentials(string username, string password)
{
    using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
    {
        if (vpc.ValidateCredentials(username, password))
        {
            using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
            {
                foreach (GroupPrincipal gp in user.GetGroups())
                {
                    try
                    {
                        if (gp.Name.Equals("Administrators"))
                        {
                            return true;
                        }
                    }
                    finally
                    {
                        gp.Dispose();
                    }
                }
            }
        }

        return false;
    } //end using PrincipalContext
}
…在另一类中:


//verify the user's password
if (!UserManagement.vpc.ValidateCredentials(username, password))
{
    string errorMsg = string.Format("Invalid credentials received for user '{0}'", username);
    throw new Exception(errorMsg);
}
(注意:我使用公共静态PrincipalContext(vpc)仅用于调用ValidateCredentials方法;我创建了一个不同的临时PrincipalContext用于创建、删除和查找用户和组,因为我在尝试使用全局PrincipalContext进行所有操作时遇到了各种与COM相关的错误)

因此,大多数情况下,这段代码工作得非常好。但是,我偶尔会遇到以下错误:

不允许同一用户使用多个用户名多次连接到服务器或共享资源。断开以前与服务器或共享资源的所有连接,然后重试。(来自HRESULT的异常:0x800704C3)

应用程序: System.DirectoryServices.AccountManagement

堆栈跟踪: 位于System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(字符串目标、字符串用户名、字符串密码) 位于System.DirectoryServices.AccountManagement.CredentialValidator.Validate(字符串用户名、字符串密码) 位于System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(字符串用户名、字符串密码) 在MyNamespace.User..ctor(字符串用户名、字符串密码)


一旦发生错误,我将继续获取它,直到重新启动整个服务器(不仅仅是IIS)。我已尝试重新启动应用程序池和/或IIS,但在重新启动计算机之前,错误不会消失。我还尝试(通过使用块)为每个ValidateCredentials调用实例化一个新的PrincipalContext(我不应该这么做),但最终还是得到了相同的错误。从我在System.DirectoryServices.AccountManagement(msdn文档,文章)上读到的内容来看,我相信我正确地使用了它,但这个错误正在削弱我的应用程序!我需要(并且应该能够)验证来自多个客户端的web服务请求的本地用户凭据。我做错什么了吗?如果您能帮助解决此问题,我们将不胜感激……

我的建议是不要与SAM提供商一起使用ValidateCredentials。我相信它应该会起作用,但我也不奇怪它会遇到问题。我认为通过使用网络登录类型对LogonUser进行p/invoke以进行编程凭证验证,您会做得更好


我想知道微软是否有一个已知的问题和解决方案,但同时我建议只是围绕这个问题进行编码。

如果您需要解决方案,即使Windows 2000和w/o作为系统运行代码,您也可以使用:

public static bool ValidateUser(
    string userName, 
    string domain, 
    string password)
{
    var tcpListener = new TcpListener(IPAddress.Loopback, 0);
    tcpListener.Start();

    var isLoggedOn = false;
    tcpListener.BeginAcceptTcpClient(
        delegate(IAsyncResult asyncResult)
        {
            using (var serverSide = 
                new NegotiateStream(
                     tcpListener.EndAcceptTcpClient(
                        asyncResult).GetStream()))
            {
                try
                {
                serverSide.AuthenticateAsServer(
                    CredentialCache.DefaultNetworkCredentials,
                    ProtectionLevel.None, 
                    TokenImpersonationLevel.Impersonation);
                var id = (WindowsIdentity)serverSide.RemoteIdentity;
                isLoggedOn = id != null;
                }
                catch (InvalidCredentialException) { }
            }
        }, null);

    var ipEndpoint = (IPEndPoint) tcpListener.LocalEndpoint;
    using (var clientSide = 
        new NegotiateStream(
            new TcpClient(
                ipEndpoint.Address.ToString(),
                ipEndpoint.Port).GetStream()))
    {
        try
        {
            clientSide.AuthenticateAsClient(
                new NetworkCredential(
                    userName,
                    password,
                    domain),
                "",
                ProtectionLevel.None,
                TokenImpersonationLevel.Impersonation);
        }
        catch(InvalidCredentialException){}
    }
    tcpListener.Stop();
    return isLoggedOn;
}
  • 此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的” 这个样板文本使许多人感到困惑。这意味着该类型公开的任何静态成员都是线程安全的。这并不意味着存储在静态成员中的任何类型的实例都是线程安全的

  • validateAdminCredentials方法创建一个新的PrincipalContext对象,然后继续使用静态vpc实例验证凭据。由于对静态实例的访问没有锁,并且实例方法不是线程安全的,因此最终会有两个线程试图同时访问同一个实例,这是行不通的

  • 尝试删除vpc字段并使用principalContext实例验证凭据。您需要在每次调用时创建并处理PrincipalContext

    此外,您可以使用IsMemberOf方法测试组中的成员资格,而不是手动迭代用户的组

    public static bool ValidateCredentials(string username, string password)
    {
        using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
        {
            return principalContext.ValidateCredentials(username, password);
        }
    }
    
    public static bool validateAdminCredentials(string username, string password)
    {
        using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
        {
            if (principalContext.ValidateCredentials(username, password))
            {
                using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
                using (GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, "Administrators"))
                {
                    if (null != group && user.IsMemberOf(group))
                    {
                        return true;
                    }
                }
            }
    
            return false;
        }
    }
    

    我通过使用域上下文解决了同样的问题。可以使用机器上下文来委托给域,但似乎最终将被迫遇到此问题

    请注意,您应该为每个调用创建新的域上下文,因为它必须使用您要验证的用户的域来创建

    我的应用程序也需要支持机器用户。我发现,只要用户是机器用户,域上下文就会抛出异常。如果捕获到异常,我将针对机器上下文工作