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

时间:2009-03-10 14:45:46

标签: 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()
{
    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);
}

(注意:我使用public static PrincipalContext(vpc)仅用于调用ValidateCredentials方法;我创建了一个不同的临时PrincipalContext,用于创建,删除和查找用户和组,因为我在收到各种与COM相关的错误时我尝试使用全局PrincipalContext来处理所有事情。)

因此,大多数情况下,此代码运行得非常好。但是,间歇性地,我收到以下错误:

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

应用: System.DirectoryServices.AccountManagement

堆栈跟踪: at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target,String userName,String password) at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName,String password) at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName,String password) at MyNamespace.User..ctor(String username,String password)

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

4 个答案:

答案 0 :(得分:2)

  1. 此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的” 这个样板文本让很多人感到困惑。这意味着类型公开的任何静态成员都是线程安全的。这并不意味着存储在静态成员中的类型的任何实例都是线程安全的。

  2. 您的 validateAdminCredentials 方法会创建一个新的 PrincipalContext 对象,但随后会继续使用静态 vpc 实例来验证凭据。由于您对静态实例的访问没有锁定,并且实例方法不是线程安全的,因此最终会让两个线程同时尝试访问同一个实例,这将无效。

  3. 尝试删除 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;
        }
    }
    

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

如果你需要一个解决方案,即使使用Windows 2000也没有运行代码作为系统,你也可以使用它:

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

答案 3 :(得分:0)

我通过使用域上下文解决了同样的问题。可以使用计算机上下文委派给域,但最终您将被迫遇到此问题。

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

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