在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) { }
}

问题

  1. 如果运行上面的示例,则在对 GetGroups 的调用中会引发 CauseNotSupportedException 方法中发生的错误。 这怎么可能? 任何理论或进一步的见解将不胜感激。
  2. 关于如何进一步调查的任何建议?
  3. 有什么比捕获异常并重试更好的建议吗? 这就是我们目前的工作。

谢谢。

澄清

我不确定我的解释有多清楚,所以这里有一些澄清。 首先,我对获取 Sids 的活动目录代码感到满意。 这做了我想要它做的事情,我认为问题不在于此。 真正的问题是,当其他不相关的代码(它不在我们的应用程序中(发生错误时,错误会在 GetGroups 调用中显现出来,因此奇怪的堆栈跟踪最初发生在 System.Web.HttpResponseStream.get_Position(( 处。 在示例应用中,NotSupportedException 发生在 CauseNotSupportedException 中,但代码不会在那里中断,它会在调用 GetGroups 时中断。 如果在示例应用中注释掉 CauseNotSupportedException((,则永远不会发生错误。

我不清楚这怎么可能发生。

在System.DirectoryServices.AccountManagement中调用UserPrinciapl.

在拨打支持电话后,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;
}