我正在使用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服务请求验证本地用户凭据。难道我做错了什么?任何有关解决这个问题的帮助都会非常感激......
答案 0 :(得分:2)
“此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的” 这个样板文本让很多人感到困惑。这意味着类型公开的任何静态成员都是线程安全的。这并不意味着存储在静态成员中的类型的任何实例都是线程安全的。
您的 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;
}
}
答案 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)
我通过使用域上下文解决了同样的问题。可以使用计算机上下文委派给域,但最终您将被迫遇到此问题。
请注意,您应为每个调用创建新的域上下文,因为它必须使用您要进行身份验证的用户的域创建。
我的应用程序也需要支持机器用户。我发现只要用户是机器用户,域上下文就会抛出异常。如果我捕获异常,我会反对机器上下文。