Как получить группы пользователя в Active Directory? (C #, asp.net)

109

Я использую этот код для получения групп текущего пользователя. Но я хочу вручную дать пользователю, а затем получить его группы. Как я могу это сделать?

using System.Security.Principal;

public ArrayList Groups()
{
    ArrayList groups = new ArrayList();

    foreach (IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
    {
        groups.Add(group.Translate(typeof(NTAccount)).ToString());
    }

    return groups;
}
Тассисто
источник

Ответы:

163

Если вы используете .NET 3.5 или System.DirectoryServices.AccountManagementновее , вы можете использовать новое пространство имен (S.DS.AM), которое делает это намного проще, чем раньше.

Прочтите об этом здесь: Управление принципами безопасности каталогов в .NET Framework 3.5

Обновление: старые статьи журнала MSDN, к сожалению, больше не доступны в Интернете - вам нужно загрузить CHM для журнала MSDN за январь 2008 года от Microsoft и прочитать там статью.

По сути, вам нужно иметь «основной контекст» (обычно ваш домен), участника-пользователя, и тогда вы очень легко получите его группы:

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
         if(p is GroupPrincipal)
         {
             result.Add((GroupPrincipal)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("department"))
       {
          result = de.Properties["department"][0].ToString();
       }
    }

    return result;
}

Обновление №2: кажется, не должно быть слишком сложно соединить эти два фрагмента кода вместе .... но хорошо - вот оно:

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;
}
marc_s
источник
@Tassisto: к сожалению, это свойство недоступно прямо на сайте UserPrincipal- см. Мой обновленный ответ, чтобы узнать, как его получить.
marc_s
Мне нужно
указать
@Tassito: ну, тогда 1) создайте контекст домена, 2) найдите этого пользователя по его имени и 3) используйте мой фрагмент кода, чтобы узнать его отдел
marc_s
1
Метод GetGroups не работал у меня, я изменил новый основной контекст, чтобы использовать другую перегрузку конструктора следующим образом: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, «192.168.2.23», «домен \ пользователь», «пароль» ); это совершенно логично, поскольку вы не всегда входите в систему через аутентификацию в активном каталоге. Надеюсь, это поможет
Омид С.
2
Это отличный ответ. Также возможно упростить итерацию групп до: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd
59

GetAuthorizationGroups()не находит вложенных групп. Чтобы действительно получить все группы, членом которых является данный пользователь (включая вложенные группы), попробуйте следующее:

using System.Security.Principal

private List<string> GetGroups(string userName)
{
    List<string> result = new List<string>();
    WindowsIdentity wi = new WindowsIdentity(userName);

    foreach (IdentityReference group in wi.Groups)
    {
        try
        {
            result.Add(group.Translate(typeof(NTAccount)).ToString());
        }
        catch (Exception ex) { }
    }
    result.Sort();
    return result;
}

Я использую, try/catchпотому что у меня были некоторые исключения с 2 из 200 групп в очень большом AD, потому что некоторые SID были больше недоступны. ( Translate()Вызов выполняет преобразование SID -> Name.)

Микки Маус
источник
3
производительность была улучшена за счет использования этой техники вместо работы через AD. Спасибо!
Филипп
GetAuthorisationGroups () очень медленный для меня, т.е. 26, и весь другой код, который я нашел до сих пор, не включает хорошо известные идентификаторы, такие как Все, Пользователи домена и т.д ... Код, который вы предоставили, буквально мгновенный и включает все sid, да только сиды но это то что мне нужно, в том числе известные и кастомные!
Тьерри
19

Прежде всего, GetAuthorizationGroups () - отличная функция, но, к сожалению, имеет 2 недостатка:

  1. Производительность оставляет желать лучшего, особенно в большой компании с большим количеством пользователей и групп. Он извлекает гораздо больше данных, чем вам действительно нужно, и выполняет вызов сервера для каждой итерации цикла в результате.
  2. Он содержит ошибки, из-за которых ваше приложение может перестать работать «когда-нибудь», когда группы и пользователи развиваются. Microsoft признала проблему и связана с некоторыми SID. Вы получите сообщение об ошибке: «Произошла ошибка при перечислении групп».

Поэтому я написал небольшую функцию для замены GetAuthorizationGroups () более производительной и безопасной. Он выполняет только 1 вызов LDAP с запросом с использованием индексированных полей. Его можно легко расширить, если вам нужно больше свойств, чем только имена групп (свойство "cn").

// Usage: GetAdGroupsForUser2("domain\user") or GetAdGroupsForUser2("user","domain")
public static List<string> GetAdGroupsForUser2(string userName, string domainName = null)
{
    var result = new List<string>();

    if (userName.Contains('\\') || userName.Contains('/'))
    {
        domainName = userName.Split(new char[] { '\\', '/' })[0];
        userName = userName.Split(new char[] { '\\', '/' })[1];
    }

    using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domainName))
        using (UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, userName))
            using (var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domainContext.Name)))
            {
                searcher.Filter = String.Format("(&(objectCategory=group)(member={0}))", user.DistinguishedName);
                searcher.SearchScope = SearchScope.Subtree;
                searcher.PropertiesToLoad.Add("cn");

                foreach (SearchResult entry in searcher.FindAll())
                    if (entry.Properties.Contains("cn"))
                        result.Add(entry.Properties["cn"][0].ToString());
            }

    return result;
}
Bigjim
источник
Потрясающие! Спасибо. Я начал писать код, использовал GetAuthorizationGroups и был в ужасе от того, что на получение всех групп уходит от 300 мс до 2,5 с. Ваш метод выполняется за 20-30 мс.
Кейт
4
Это казалось многообещающим, но не разрешает вложенные группы, например, пользователь является членом группы a, которая сама является членом группы x. В приведенном выше коде будет отображаться только группа a, но не группа x. Я использовал этот метод через tokenGroups: stackoverflow.com/a/4460658/602449
Роберт Мюхсиг 01
Взгляните на комментарий Роберта Мюзига - это делает вложенные группы и даже быстрее. Единственный недостаток: он возвращает только группы безопасности, а не группы
рассылки,
@bigjim Невозможно использовать GetAuthorizationGroups, поскольку для возврата данных требуется около 6 секунд, но предоставленный вами код не возвращает хорошо известные группы, такие как «Все», «Пользователи домена» и т. д., и они мне нужны. Кажется, что все там возвращает только «настраиваемые группы», а не все группы, к которым принадлежит пользователь.
Тьерри
11

В AD у каждого пользователя есть собственность memberOf. Он содержит список всех групп, к которым он принадлежит.

Вот небольшой пример кода:

// (replace "part_of_user_name" with some partial user name existing in your AD)
var userNameContains = "part_of_user_name";

var identity = WindowsIdentity.GetCurrent().User;
var allDomains = Forest.GetCurrentForest().Domains.Cast<Domain>();

var allSearcher = allDomains.Select(domain =>
{
    var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain.Name));

    // Apply some filter to focus on only some specfic objects
    searcher.Filter = String.Format("(&(&(objectCategory=person)(objectClass=user)(name=*{0}*)))", userNameContains);
    return searcher;
});

var directoryEntriesFound = allSearcher
    .SelectMany(searcher => searcher.FindAll()
        .Cast<SearchResult>()
        .Select(result => result.GetDirectoryEntry()));

var memberOf = directoryEntriesFound.Select(entry =>
{
    using (entry)
    {
        return new
        {
            Name = entry.Name,
            GroupName = ((object[])entry.Properties["MemberOf"].Value).Select(obj => obj.ToString())
        };
    }
});

foreach (var item in memberOf)
{
    Debug.Print("Name = " + item.Name);
    Debug.Print("Member of:");

    foreach (var groupName in item.GroupName)
    {
        Debug.Print("   " + groupName);
    }

    Debug.Print(String.Empty);
}
}
Оливер
источник
1
@Tassisto: Да, он вас понимает. Приведенный выше фрагмент кода будет работать именно так, как вам нравится. Просто замените последний цикл foreach на цикл, который генерирует список имен групп вместо отладочной печати.
Joel Etherton
2
Он не сможет указать основную группу пользователя (часто это пользователи домена). Вы должны вернуться и запросить эту информацию отдельно. GetAuthorizationGroups не имеет этой проблемы.
Энди
1

В моем случае единственным способом, которым я мог продолжать использовать GetGroups () без каких-либо ограничений, было добавление пользователя (USER_WITH_PERMISSION) в группу, у которой есть разрешение на чтение AD (Active Directory). Чрезвычайно важно создать PrincipalContext, передающий этого пользователя и пароль.

var pc = new PrincipalContext(ContextType.Domain, domain, "USER_WITH_PERMISSION", "PASS");
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
var groups = user.GetGroups();

Действия, которые вы можете выполнить в Active Directory, чтобы заставить его работать:

  1. В Active Directory создайте группу (или возьмите одну) и на вкладке «Безопасность» добавьте «Windows Authorization Access Group»
  2. Нажмите кнопку «Дополнительно»
  3. Выберите «Группа авторизации доступа Windows» и нажмите «Просмотр».
  4. Установите флажок «Читать tokenGroupsGlobalAndUniversal»
  5. Найдите нужного пользователя и добавьте его в группу, которую вы создали (взяты) с первого шага
Гандарес
источник
1
Это, вероятно, вступит в игру, если вы используете встроенные учетные записи для учетной записи пула служб / приложений в своем веб-приложении. Если вы используете учетную запись домена в качестве учетной записи пула служб / приложений или олицетворяете учетную запись домена в коде, она должна иметь права чтения по умолчанию и не иметь этой проблемы.
vapcguy 04
1

Это работает для меня

public string[] GetGroupNames(string domainName, string userName)
    {
        List<string> result = new List<string>();

        using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domainName))
        {
            using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(principalContext, userName).GetGroups())
            {
                src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
            }
        }

        return result.ToArray();
    }
Таран
источник
1

Ответ зависит от того, какие группы вы хотите получить. Пространство System.DirectoryServices.AccountManagementимен предоставляет два метода группового поиска:

GetGroups - возвращает коллекцию групповых объектов, которые определяют группы, членом которых является текущий участник.

Этот перегруженный метод возвращает только группы, участником которых является участник; рекурсивный поиск не выполняется.

GetAuthorizationGroups - возвращает коллекцию основных объектов, содержащую все группы авторизации, членом которых является данный пользователь. Эта функция возвращает только группы, которые являются группами безопасности; группы рассылки не возвращаются.

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

Так GetGroupsстановится все группы , в которые пользователь является прямым участником, и GetAuthorizationGroupsполучает все авторизации группы из которых пользователь является прямым или косвенным членом.

Несмотря на то, как они названы, одно не является подмножеством другого. Могут быть группы, возвращенные GetGroupsне возвращенным пользователем GetAuthorizationGroups, и наоборот.

Вот пример использования:

PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "MyDomain", "OU=AllUsers,DC=MyDomain,DC=Local");
UserPrincipal inputUser = new UserPrincipal(domainContext);
inputUser.SamAccountName = "bsmith";
PrincipalSearcher adSearcher = new PrincipalSearcher(inputUser);
inputUser = (UserPrincipal)adSearcher.FindAll().ElementAt(0);
var userGroups = inputUser.GetGroups();
Таваб Вакил
источник
1

Мое решение:

UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, myDomain), IdentityType.SamAccountName, myUser);
List<string> UserADGroups = new List<string>();            
foreach (GroupPrincipal group in user.GetGroups())
{
    UserADGroups.Add(group.ToString());
}
Дарку
источник
0

Если Translate работает локально, но не удаленно, группа ei. Перевести (typeof (NTAccount)

Если вы хотите, чтобы код приложения выполнялся с использованием идентификатора LOGGED IN USER, включите олицетворение. Олицетворение можно включить через IIS или добавив следующий элемент в файл web.config .

<system.web>
<identity impersonate="true"/>

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

Благодарим PRAGIM tech за эту информацию из его прилежного видео.

Аутентификация Windows в asp.net, часть 87:

https://www.youtube.com/watch?v=zftmaZ3ySMc

Но выдача себя за другое лицо создает много накладных расходов на сервере.

Лучшее решение, позволяющее пользователям определенных сетевых групп - запретить анонимность в веб-конфигурации. <authorization><deny users="?"/><authentication mode="Windows"/>

и в вашем коде позади, предпочтительно в global.asax, используйте HttpContext.Current.User.IsInRole :

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
If HttpContext.Current.User.IsInRole("TheDomain\TheGroup") Then
//code to do when user is in group
End If

ПРИМЕЧАНИЕ: Группа должна быть написана с обратной косой чертой \ т.е. "TheDomain \ TheGroup"

Пьер-Давид Сабурин
источник