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

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

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

……在另一个class级:

 //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)。

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

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

应用程序:System.DirectoryServices.AccountManagement

堆栈跟踪:位于System.DirectoryServices.AccountManagement.CredentialValidator.Validate的System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target,String userName,String password),System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials上的字符串userName,String password MyNamespace.User..ctor上的(字符串userName,String password)(字符串用户名,字符串密码)

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

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

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

尝试删除vpc字段并使用principalContext实例validation凭据。 您需要在每次调用时创建和处理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; } } 

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

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

如果您需要一个解决方案,即使使用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; } 

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

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

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