C# 使用C查找递归组成员身份(Active Directory)#

C# 使用C查找递归组成员身份(Active Directory)#,c#,.net,active-directory,C#,.net,Active Directory,我希望在Active Directory中获得用户所属的所有组的列表,这些组既显式地列在memberOf属性列表中,也隐式地通过嵌套组成员身份列出。例如,如果我检查UserA,并且UserA是GroupA和GroupB的一部分,那么如果GroupB是GroupC的成员,我还想列出GroupC 为了让您对我的应用程序有更多的了解,我将在有限的基础上进行此操作。基本上,我希望偶尔进行一次安全检查,列出这些额外的会员资格。我想区分两者,但这并不难 我的问题是,我还没有找到一种有效的方法来实现这个查询。

我希望在Active Directory中获得用户所属的所有组的列表,这些组既显式地列在memberOf属性列表中,也隐式地通过嵌套组成员身份列出。例如,如果我检查UserA,并且UserA是GroupA和GroupB的一部分,那么如果GroupB是GroupC的成员,我还想列出GroupC

为了让您对我的应用程序有更多的了解,我将在有限的基础上进行此操作。基本上,我希望偶尔进行一次安全检查,列出这些额外的会员资格。我想区分两者,但这并不难

我的问题是,我还没有找到一种有效的方法来实现这个查询。Active Directory()上的标准文本显示了一种基本上是递归查找的方法。这似乎效率极低。即使在我的小域名中,用户也可能拥有30多个组成员。这意味着一个用户可以调用30多次Active Directory

我研究了以下LDAP代码,以一次获取所有memberOf条目:

(memberOf:1.2.840.113556.1.4.1941:={0})
(memberOf:1.2.840.113556.1.4.1941:={0})
其中{0}将是我的LDAP路径(例如:CN=UserA,OU=Users,DC=foo,DC=org)。但是,它不会返回任何记录。这种方法的缺点是,即使有效,我也不知道哪一组是显式的,哪一组是隐式的


这就是我目前所拥有的。我想知道是否有比CodeProject文章更好的方法,如果是的话,如何实现(实际的代码会很棒)。我正在使用.NET4.0和C#。我的Active Directory处于Windows 2008功能级别(它还不是R2)。

如果除了递归调用之外没有其他方法(我相信没有),那么至少可以让框架为您完成工作:请参阅(在
System.DirectoryServices.AccountManagement
命名空间中,并在.Net 3.5中介绍)

此方法搜索所有组 以递归方式返回组 用户是其中的一个成员。这个 返回的集合还可能包括 该系统将支持的其他组 考虑用户的成员 授权目的


将结果与(“返回指定当前主体是其成员的组的组对象集合”)进行比较,以查看成员身份是显式的还是隐式的。

谢谢这是一个有趣的问题

接下来,只是一个更正,你说:

我研究了以下LDAP代码,以一次获取所有memberOf条目:

(memberOf:1.2.840.113556.1.4.1941:={0})
(memberOf:1.2.840.113556.1.4.1941:={0})
你不能让它工作。我记得当我知道它的存在时,我让它工作了,但它是在一个LDIFDE.EXE过滤器中。所以我把它应用到C#中的ADSI中,它仍然在工作。我从Microsoft获取的示例中有太多的括号,但它仍然有效()

根据您关于我们不知道用户是否明确属于该组这一事实的评论,我再添加一个请求。我知道这不是很好,但这是我能做的最好的了

static void Main(string[] args)
{
  /* Connection to Active Directory
   */
  DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr");


  /* To find all the groups that "user1" is a member of :
   * Set the base to the groups container DN; for example root DN (dc=dom,dc=fr) 
   * Set the scope to subtree
   * Use the following filter :
   * (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x)
   */
  DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
  dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
  dsLookFor.SearchScope = SearchScope.Subtree;
  dsLookFor.PropertiesToLoad.Add("cn");

  SearchResultCollection srcGroups = dsLookFor.FindAll();

  /* Just to know if user is explicitly in group
   */
  foreach (SearchResult srcGroup in srcGroups)
  {
    Console.WriteLine("{0}", srcGroup.Path);

    foreach (string property in srcGroup.Properties.PropertyNames)
    {
      Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]);
    }

    DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path);
    DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup);
    dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
    dsLookForAMermber.SearchScope = SearchScope.Base;
    dsLookForAMermber.PropertiesToLoad.Add("cn");

    SearchResultCollection memberInGroup = dsLookForAMermber.FindAll();
    Console.WriteLine("Find the user {0}", memberInGroup.Count);

  }

  Console.ReadLine();
}
在我的测试树中,给出:

LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
cn : MonGrpSec
Find the user 1

LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpDis
Find the user 1

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSec
Find the user 0

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSecUniv
Find the user 0

(编辑)
“1.2.840.113556.1.4.1941”在W2K3 SP1中不起作用,它开始在SP2中起作用。我想这和W2K3R2是一样的。它应该在W2K8上工作。我在这里测试W2K8R2。我很快就能在W2K8上测试这一点。

递归使用ldap过滤器,但每次查询后返回的所有组的查询都会减少往返次数

例:

  • 获取用户为成员的所有组
  • 获取步骤1组为成员的所有组
  • 获取步骤2组为成员的所有组
  • 根据我的经验,很少有超过5个,但肯定应该大大少于30个

    此外:

    • 确保只拉属性 你需要回来
    • 缓存结果可以极大地帮助您 性能,但使我的代码 更复杂
    • 确保使用连接池
    • 第一组必须单独处理
    静态列表和查找所有成员(字符串a\u sSearchRoot、字符串a\u sGroupDN、字符串[]a\u asPropsToLoad)
    {
    使用(DirectoryEntry de=newdirectoryEntry(a_-sSearchRoot))
    返回ad_find_all_成员(de、a_sGroupDN、a_asPropsToLoad);
    }
    静态列表ad_find_all_成员(目录条目a_SearchRoot,字符串a_sGroupDN,字符串[]a_asPropsToLoad)
    {
    字符串sDN=“DifferentiedName”;
    字符串sOC=“objectClass”;
    字符串sOC_GROUP=“GROUP”;
    字符串[]asPropsToLoad=a_asPropsToLoad;
    Array.Sort(asPropsToLoad);
    if(Array.BinarySearch(asPropsToLoad,sDN)<0)
    {
    调整数组大小(参考asPropsToLoad,asPropsToLoad.Length+1);
    asPropsToLoad[asPropsToLoad.Length-1]=sDN;
    }
    if(Array.BinarySearch(asPropsToLoad,sOC)<0)
    {
    调整数组大小(参考asPropsToLoad,asPropsToLoad.Length+1);
    asPropsToLoad[asPropsToLoad.Length-1]=sOC;
    }
    List lsr=新列表();
    使用(DirectorySearcher ds=新的DirectorySearcher(a_SearchRoot))
    {
    ds.Filter=“(&(|(objectClass=group)(objectClass=user))(memberOf=“+a_sGroupDN+”)”;
    ds.PropertiesToLoad.Clear();
    ds.PropertiesToLoad.AddRange(asPropsToLoad);
    ds.PageSize=1000;
    ds.SizeLimit=0;
    foreach(ds.FindAll()中的SearchResult sr)
    lsr.Add(sr);
    }
    
    对于(int i=0;i,如果您在Exchange server上,则可以使用令牌组和令牌组全局通用属性。 令牌组将为您提供该用户所属的所有安全组,包括嵌套组和域用户、用户等 tokenGroupsGlobalAndUniversal将包括从tokenGroups到distribution Group的所有内容

    private void DoWorkWithUserGroups(string domain, string user)
        {
            var groupType = "tokenGroupsGlobalAndUniversal"; // use tokenGroups for only security groups
    
            using (var userContext = new PrincipalContext(ContextType.Domain, domain))
            {
                using (var identity = UserPrincipal.FindByIdentity(userContext, IdentityType.SamAccountName, user))
                {
                    if (identity == null)
                        return;
    
                    var userEntry = identity.GetUnderlyingObject() as DirectoryEntry;
                    userEntry.RefreshCache(new[] { groupType });
                    var sids = from byte[] sid in userEntry.Properties[groupType]
                               select new SecurityIdentifier(sid, 0);
    
                    foreach (var sid in sids)
                    {
                        using(var groupIdentity = GroupPrincipal.FindByIdentity(userContext, IdentityType.Sid, sid.ToString()))
                        {
                            if(groupIdentity == null)
                                continue; // this group is not in the domain, probably from sidhistory
    
                            // extract the info you want from the group
                        }
                    }
                }
            }
        }
    

    如果您使用的是.NET 3.5或更高版本,则可以使用
    System.DirectoryServices.AccountManagement
    名称空间,这样做非常简单

    请参见此处的相关答案: