[英]Strange intermittent error handling bug when calling UserPrinciapl.GetGroups in System.DirectoryServices.AccountManagement
[英]Strange Error When Using System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials with SAM
我正在使用 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
}
...在另一個 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);
}
(注意:我使用公共 static PrincipalContext (vpc) 僅用於調用 ValidateCredentials 方法;我創建了一個不同的臨時 PrincipalContext 來創建、刪除和查找用戶和組,因為我在嘗試使用時遇到了各種與 COM 相關的錯誤所有事物的全局 PrincipalContext )。
所以,大多數時候,這段代碼運行得非常好。 但是,間歇性地,我收到以下錯誤:
不允許同一用戶使用多個用戶名與服務器或共享資源建立多個連接。 斷開與服務器或共享資源的所有先前連接,然后重試。 (來自 HRESULT 的異常:0x800704C3)
應用程序:System.DirectoryServices.AccountManagement
堆棧跟蹤:在 System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password) 在 System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials 的 System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password) (字符串用戶名,字符串密碼)在 MyNamespace.User..ctor(字符串用戶名,字符串密碼)
一旦發生錯誤,我會繼續得到它,直到我重新啟動我的整個服務器(不僅僅是 IIS)。 我已經嘗試重新啟動我的應用程序池和/或 IIS,但在我重新啟動機器之前,錯誤不會消失 go。 我還嘗試為每次調用 ValidateCredentials (我不應該這樣做)實例化(通過 using 塊)一個新的 PrincipalContext,但我最終仍然得到相同的錯誤。 根據我在 System.DirectoryServices.AccountManagement (msdn docs,articles)上閱讀的內容,我相信我使用它是正確的,但是這個錯誤正在削弱我的應用程序。 我需要(並且應該能夠)驗證來自多個客戶端的 web 服務請求的本地用戶憑據? 難道我做錯了什么。 任何有關解決此問題的幫助將不勝感激...
“任何這種類型的公共 static(在 Visual Basic 中共享)成員都是線程安全的”這個樣板文本讓很多人感到困惑。 這意味着該類型公開的任何 static 成員都是線程安全的。 這並不意味着存儲在 static 成員中的類型的任何實例都是線程安全的。
您的validateAdminCredentials方法會創建一個新的PrincipalContext object,然后繼續使用static vpc實例來驗證憑據。 由於您對 static 實例的訪問沒有鎖定,並且實例方法不是線程安全的,因此您最終將有兩個線程嘗試同時訪問同一個實例,這將不起作用。
嘗試刪除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;
}
}
我的建議是不要將 ValidateCredentials 與 SAM 提供程序一起使用。 我相信它應該可以工作,但我對它遇到問題也不感到驚訝。 我認為通過使用網絡登錄類型對 LogonUser 進行 ap/invoke 以進行編程憑據驗證,您會做得更好。
我想看看微軟是否有一個已知問題和解決這個問題的方法,但同時我建議只是圍繞它進行編碼。
如果您需要一個解決方案,即使使用 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;
}
我通過使用域上下文解決了同樣的問題。 可以使用機器上下文委托給域,但看起來您最終將被迫遇到這個問題。
請注意,您應該為每個調用創建新的域上下文,因為它必須使用您要驗證的用戶的域來創建。
我的應用程序也需要支持機器用戶。 我發現只要用戶是機器用戶,域上下文就會拋出異常。 如果我捕捉到異常,我會針對機器上下文工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.