在System.DirectoryServices.AccountManagement中调用UserPrinciapl.
本文关键字:调用 UserPrinciapl AccountManagement System DirectoryServices | 更新日期: 2023-09-27 18:19:20
Background
我们有一个用 C# 编写的 asp.net 4.0 Web 应用程序,它调用用 C# 编写的 .net 3.5 Web 服务。 Web 服务将传递一个用户 ID,并根据用户所属的活动目录组返回数据列表。
Web 服务使用 .net 3.5 版本的 System.DirectoryServices.AccountManagement 来获取用户所属组的 Sid。
对 UserPrincipal.GetGroups 的调用间歇性失败,并显示以下错误。 两次发生之间有很长的时间,但当它确实发生时,它会反复发生几分钟。 不同的 AD 用户出现此问题。
此异常的堆栈跟踪对我们来说毫无意义。 我们花了很多时间查看 Reflector/ILSpy 中的 Microsoft AD 代码,但无法超越对 IADsPathName.Retrieve 的调用。
例外
System.NotSupportedException: Specified method is not supported.
at System.Web.HttpResponseStream.get_Position()
at System.Drawing.UnsafeNativeMethods.ComStreamFromDataStream.Seek(Int64 offset, Int32 origin)
at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType)
at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()
at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsForestName()
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOf(Principal p)
at System.DirectoryServices.AccountManagement.Principal.GetGroupsHelper()
at System.DirectoryServices.AccountManagement.Principal.GetGroups()
at Data.SoftwarePublishingItemData.GetSids(String requestedForUserId)
at Data.SoftwarePublishingItemData.GetSoftwarePublishingItems(IDatabaseContext dbContext, GetSoftwarePublishingItemsSettings settings, XBXmlDocument parameters)
at Web.GetSoftwarePublishingItems.GetFlexiFieldData(String xml)
要重现的代码
请注意,CauseNotSupportedException 方法模拟的代码不是在我们的应用程序中运行,而是在我们无法控制的环境中其他位置的代码中运行。
class Program
{
static void Main(string[] args)
{
CauseNotSupportedException();
string samAccountName = "domain.user";
using (var principalContext = new PrincipalContext(ContextType.Domain))
{
using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName))
{
if (userPrincipal == null)
throw new ActiveDirectoryObjectNotFoundException();
using (var groups = userPrincipal.GetGroups())
{
foreach (GroupPrincipal group in groups)
{
Console.WriteLine(group.Sid);
}
}
}
}
}
public static void CauseNotSupportedException()
{
using (var b = new Bitmap(500, 500, PixelFormat.Format32bppArgb))
{
b.Save(new FakeStream(), ImageFormat.Png);
}
}
}
实现流以模仿 HttpResponseStream 行为
public class FakeStream : Stream
{
public override bool CanRead { get { return false; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return true; } }
public override void Flush() { }
public override long Length { get { throw new NotSupportedException("No Seek"); } }
public override long Position
{
get { throw new NotSupportedException("No Seek"); }
set { throw new NotSupportedException("No Seek"); }
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Write only stream");
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("net_noseek");
}
public override void SetLength(long value) { }
public override void Write(byte[] buffer, int offset, int count) { }
}
问题
- 如果运行上面的示例,则在对 GetGroups 的调用中会引发 CauseNotSupportedException 方法中发生的错误。 这怎么可能? 任何理论或进一步的见解将不胜感激。
- 关于如何进一步调查的任何建议?
- 有什么比捕获异常并重试更好的建议吗? 这就是我们目前的工作。
谢谢。
澄清
我不确定我的解释有多清楚,所以这里有一些澄清。 首先,我对获取 Sids 的活动目录代码感到满意。 这做了我想要它做的事情,我认为问题不在于此。 真正的问题是,当其他不相关的代码(它不在我们的应用程序中(发生错误时,错误会在 GetGroups 调用中显现出来,因此奇怪的堆栈跟踪最初发生在 System.Web.HttpResponseStream.get_Position(( 处。 在示例应用中,NotSupportedException 发生在 CauseNotSupportedException 中,但代码不会在那里中断,它会在调用 GetGroups 时中断。 如果在示例应用中注释掉 CauseNotSupportedException((,则永远不会发生错误。
我不清楚这怎么可能发生。
在拨打支持电话后,Microsoft发布了此问题的热修复程序。 请参阅下面的链接。
所述原因是:出现此问题的原因是 System.DirectoryServices.AccountManagement 命名空间是本机 API Active Directory 服务接口 (ADSI( 的精简包装器。由 IADsPathName 接口实现的 IErrorInfo 接口响应 ADSI 不会引发的异常。当堆栈上没有 ADSI 异常时,IErrorInfo 接口会引发堆栈顶部的异常,即使异常由应用程序中的另一个处理程序处理也是如此。
http://support.microsoft.com/kb/2683913
感谢那些提出建议的人。
如果您使用的是 .NET 3.5 或更高版本,则可以使用新的 System.DirectoryServices.AccountManagement
(S.DS.AM( 命名空间,这使得这比以前容易得多。
在此处阅读所有相关信息:[在.NET Framework 3.5中管理目录安全主体][1]
基本上,您需要有一个"主体上下文"(通常是您的域(、一个用户主体,然后您很容易获得其组:
public List<GroupPrincipal> GetGroups(string userName)
{
List<GroupPrincipal> result = new List<GroupPrincipal>();
// establish domain context
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find your user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);
// if found - grab its groups
if(user != null)
{
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
// iterate over all groups
foreach(Principal p in groups)
{
// make sure to add only group principals or change this to add to a list or varible if needed.
if(p is GroupPrincipal)
{
result.Add(p);
}
}
}
return result;
}
为了访问某些未显示在UserPrincipal
对象上的属性,您需要深入了解底层DirectoryEntry
:
public string GetDepartment(Principal principal)
{
string result = string.Empty;
DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("samAccountName"))
{
result = de.Properties["samAccountName"][0].ToString();
}
}
return result;
}
//Change this Method to fit what ever your needs desire..
public string GetDepartment(string username)
{
string result = string.Empty;
// if you do repeated domain access, you might want to do this *once* outside this method,
// and pass it in as a second parameter!
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);
// if user is found
if(user != null)
{
// get DirectoryEntry underlying it
DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
if (de.Properties.Contains("department"))
{
result = de.Properties["department"][0].ToString();
}
}
}
return result;
}