Быстрый способ получить список членов группы в Active Directory с помощью C # - PullRequest
6 голосов
/ 25 июня 2011

В веб-приложении мы ищем список учетных записей sam для пользователей, которые являются членами определенной группы. Группы могут иметь 500 или более участников во многих случаях, и нам нужно, чтобы страница была отзывчивой.

Для группы из 500 человек требуется 7-8 секунд, чтобы получить список учетных записей sam для всех членов группы. Есть ли более быстрые способы? Я знаю, что консоль управления Active Directory делает это менее чем за секунду.

Я пробовал несколько методов:

1)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
List<string> lst = grp.Members.Select(g => g.SamAccountName).ToList();

2)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
PrincipalSearchResult<Principal> lstMembers = grp.GetMembers(true);
List<string> lst = new List<string>();
foreach (Principal member in lstMembers )
{
    if (member.StructuralObjectClass.Equals("user"))
    {
        lst.Add(member .SamAccountName);
    }
}

3)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
System.DirectoryServices.DirectoryEntry de = (System.DirectoryServices.DirectoryEntry)grp.GetUnderlyingObject();
List<string> lst = new List<string>();
foreach (string sDN in de.Properties["member"])
{
    System.DirectoryServices.DirectoryEntry deMember = new System.DirectoryServices.DirectoryEntry("LDAP://" + sDN);
    lst.Add(deMember.Properties["samAccountName"].Value.ToString());
}

Ответы [ 6 ]

5 голосов
/ 25 июня 2011

У моего коллеги были похожие проблемы со временем запроса при использовании различных методов поиска Active Directory.В итоге он кэшировал информацию в базе данных и обновлял ее по ночам, а затем просто получал доступ к базе данных.

Учитывая тот факт, что учетные записи пользователей меняются не так часто, это был приемлемый компромисс для него.В зависимости от вашего использования это может или не может быть приемлемым.

4 голосов
/ 27 июня 2011

Вот рекурсивный поиск (поиск пользователей во вложенных группах) с использованием ADSI.

static void Main(string[] args)
{
  /* Connection to Active Directory
   */
  string sFromWhere = "LDAP://SRVENTR2:389/dc=societe,dc=fr";
  DirectoryEntry deBase = new DirectoryEntry(sFromWhere, "societe\\administrateur", "test.2011");

  /* To find all the users member of groups "Grp1"  :
   * Set the base to the groups container DN; for example root DN (dc=societe,dc=fr) 
   * Set the scope to subtree
   * Use the following filter :
   * (member:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=X)
   */
  DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
  dsLookFor.Filter = "(&(memberof:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=societe,DC=fr)(objectCategory=user))";
  dsLookFor.SearchScope = SearchScope.Subtree;
  dsLookFor.PropertiesToLoad.Add("cn");
  dsLookFor.PropertiesToLoad.Add("samAccountName");  

  SearchResultCollection srcUsers = dsLookFor.FindAll();

  /* Just show each user
   */
  foreach (SearchResult srcUser in srcUsers)
  {
    Console.WriteLine("{0}", srcUser.Path);
    Console.WriteLine("{0}", srcUser.Properties["samAccountName"][0]);
  }

  Console.ReadLine();

}


Для комментария @Gabriel Luci: документация Microsoft

memberOf

Атрибут memberOf является многозначным атрибутом, который содержит группы, членом которых является пользователь, за исключением первичной группы, которая представлена ​​primaryGroupId,Членство в группе зависит от контроллера домена (DC), из которого извлекается этот атрибут:

  • На DC для домена, который содержит пользователя, memberOf для пользователя завершается по отношению кчленство в группах в этом домене;однако memberOf не содержит членства пользователя в локальных и глобальных группах домена в других доменах.

  • На сервере GC memberOf для пользователя завершается в отношении всех членств в универсальных группах.Если оба условия выполняются для DC, оба набора данных содержатся в memberOf.

Имейте в виду, что этот атрибут перечисляет группы, которые содержат пользователя в их атрибуте member - он несодержит рекурсивный список вложенных предшественников.Например, если пользователь O является членом группы C, а группа B и группа B были вложены в группу A, атрибут memberOf пользователя O будет содержать список группы C и группы B, но не группы A.

Thisатрибут не сохраняется - это вычисляемый атрибут обратной ссылки.

1 голос
/ 06 июня 2014

Попробуйте, но не уверены, что это будет быстрее, но ....

        PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)

        GroupPrincipal mygroup = new GroupPrincipal(pcRoot);

        // define the principal searcher, based on that example principal

        PrincipalSearcher ps = new PrincipalSearcher(mygroup);

        ps.QueryFilter = new GroupPrincipal(pcRoot) { SamAccountName = "Name of your group Case Sensitive" };

        List<UserPrincipal> users = new List<UserPrincipal>();
        // loop over all principals found by the searcher

        GroupPrincipal foundGroup = (GroupPrincipal)ps.FindOne();

foreach (UserPrincipal u in foundGroup.Members) 
                    {
                        users.Add(u);

                    }
//OR
List<string> lst = foundGroup.Members.Select(g => g.SamAccountName).ToList();//this will only get the usernames not the user object or UserPrincipal
0 голосов
/ 05 апреля 2018

Если вам нужна скорость, вообще не используйте пространство имен System.DirectoryServices.AccountManagement (GroupPrincipal, UserPrincipal и т. Д.). Это облегчает кодирование, но это slloooowwww.

Используйте только DirectorySearcher и DirectoryEntry. (Пространство имен AccountManagement это просто оболочка для этого)

Я имел эту беседу с кем-то еще не так давно. Вы можете прочитать полный чат здесь , но в одном случае, когда в группе было 4873 члена, метод AccountManagement GetMember() занял 200 секунд, тогда как использование DirectoryEntry заняло всего 16 секунд.

Однако есть несколько предостережений:

  1. Не смотрите на атрибут memberOf (как подсказывает ответ JPBlanc). Он не найдет членов группы Local Local. Атрибут memberOf показывает только универсальные группы и глобальные группы только в одном домене. Домен Локальные группы там не отображаются.
  2. Просмотр атрибута member группы даст вам только 1500 участников за раз. Вы должны получить членов в партиях по 1500.
  3. Учетная запись может иметь для primaryGroupId любую группу и считаться частью этой группы (но не отображаться в атрибуте member этой группы). Обычно это относится только к группе Domain Users.
  4. Если в локальной доменной группе есть пользователи из внешнего домена, они отображаются как объекты принципала внешней безопасности, которые содержат SID для фактической учетной записи во внешнем домене. Необходимо выполнить дополнительную работу, чтобы найти учетную запись во внешнем домене.

Метод GetMember() пространства имен AccountManagement заботится обо всех этих вещах, но не так эффективно, как мог бы.

Помогая этому другому пользователю, я разработал метод, который охватит первые три проблемы выше, но не № 4. Это последний блок кода в этом ответе: https://stackoverflow.com/a/49241443/1202807

Обновление: Вы упомянули, что самая трудоемкая часть - это циклический просмотр членов. Это потому, что вы привязаны к каждому члену, что понятно. Вы можете уменьшить это, вызвав .RefreshCache() для объекта DirectoryEntry, чтобы загрузить только нужные вам свойства. В противном случае, когда вы впервые используете Properties, он получит каждый атрибут со значением, которое добавляет время без причины.

Ниже приведен пример, который я использовал. Я провел тестирование с группой, состоящей из 803 участников (во вложенных группах), и обнаружил, что линии .RefreshCache() постоянно сбриваются примерно на 10 секунд, если не больше (~ 60 с без, ~ 45-50 с с).

Этот метод не учитывает пункты 3 и 4, которые я упомянул выше. Например, он будет молчаливо игнорировать принципы внешней безопасности. Но если у вас есть только один домен без трастов, вам не нужно об этом заботиться.

private static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false) {
    var members = new List<string>();

    group.RefreshCache(new[] { "member" });

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            var memberDe = new DirectoryEntry($"LDAP://{member}");
            memberDe.RefreshCache(new[] { "objectClass", "sAMAccountName" });
            if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
                members.AddRange(GetGroupMemberList(memberDe, true));
            } else {
                var username = memberDe.Properties["sAMAccountName"]?.Value?.ToString();
                if (!string.IsNullOrEmpty(username)) { //It will be null if this is a Foreign Security Principal
                    members.Add(username);
                }
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }

    return members;
}
0 голосов
/ 20 мая 2015

Как и в вашем первом варианте, я создал хэш-сет из списка. Чем больше группа, тем больше времени требуется для проверки членства. Однако это соответствует для успешных и неудачных запросов членства. Итерации по большой группе иногда занимают в 3 раза больше времени, если учетная запись не является участником, тогда как этот метод всегда одинаков.

using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
using(GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, "groupName"))
{
    List<string> members = group.GetMembers(true).Select(g => g.SamAccountName).ToList();
    HashSet<string> hashset = new HashSet<string>(members, StringComparer.OrdinalIgnoreCase);

    if(hashset.Contains(someUser)
        return true;
}

Членство в кеширующей группе

Членство в группах в Active Directory не должно часто меняться. По этой причине рассмотрите возможность кэширования членства в группах, чтобы ускорить поиск. Затем обновляйте членство в кэшированной группе каждый час или что-либо еще, что наиболее подходит для вашей среды. Это значительно повысит производительность и уменьшит перегрузку в сети и контроллерах домена.

Одно предупреждение , если важная / ограниченная информация защищена и существует необходимость в более строгих мерах безопасности. Тогда прямой запрос к Active Directory - это путь, который обеспечивает самую актуальную информацию о членстве.

0 голосов
/ 25 июня 2011

Вы пробовали запрос LDAP? Внизу страницы есть пример в C # для перечисления через группу, чтобы получить членов. MSDN BOL

using System;
using System.DirectoryServices;

namespace ADAM_Examples
{
class EnumMembers
{
    /// <summary>
    /// Enumerate AD LDS groups and group members.
    /// </summary>
    [STAThread]
    static void Main()
    {
        DirectoryEntry objADAM;                   // Binding object.
        DirectoryEntry objGroupEntry;             // Group Results.
        DirectorySearcher objSearchADAM;          // Search object.
        SearchResultCollection objSearchResults;  // Results collection.
        string strPath;                           // Binding path.

        // Construct the binding string.
        strPath = "LDAP://localhost:389/OU=TestOU,O=Fabrikam,C=US";
        Console.WriteLine("Bind to: {0}", strPath);
        Console.WriteLine("Enum:    Groups and members.");

        // Get the AD LDS object.
        try
        {
            objADAM = new DirectoryEntry(strPath);
            objADAM.RefreshCache();
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Bind failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        // Get search object, specify filter and scope,
        // perform search.
        try
        {
            objSearchADAM = new DirectorySearcher(objADAM);
            objSearchADAM.Filter = "(&(objectClass=group))";
            objSearchADAM.SearchScope = SearchScope.Subtree;
            objSearchResults = objSearchADAM.FindAll();
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Search failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        // Enumerate groups and members.
        try
        {
            if (objSearchResults.Count != 0)
            {
                foreach(SearchResult objResult in objSearchResults)
                {
                    objGroupEntry = objResult.GetDirectoryEntry();
                    Console.WriteLine("Group    {0}",
                        objGroupEntry.Name);
                    foreach(object objMember
                        in objGroupEntry.Properties["member"])
                    {
                        Console.WriteLine(" Member: {0}",
                            objMember.ToString());
                    }
                }
            }
            else
            {
                Console.WriteLine("Results: No groups found.");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Enumerate failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        Console.WriteLine("Success: Enumeration complete.");
        return;
    }
}

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...