如何在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;
}

Answers:


163

如果您使用的是.NET 3.5或更高版本,则可以使用新的System.DirectoryServices.AccountManagement(S.DS.AM)名称空间,这比以前更容易了。

在这里阅读有关它的所有信息:在.NET Framework 3.5中管理目录安全性主体

更新:不幸的是,较早的MSDN杂志文章不再在线上-您需要从Microsoft 下载2008年1月MSDN杂志的CHM并在其中阅读该文章。

基本上,您需要有一个“主要上下文”(通常是您的域),一个用户主体,然​​后您可以很容易地获得其组:

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;
}

@Tassisto:不幸的是,该属性无法直接在上使用UserPrincipal-有关如何获取该属性的信息,请参阅我的最新答案。
marc_s

我需要提供用户名以获取其
出发地

@Tassito:那么1)创建一个域上下文,2)通过其名称找到该用户,3)使用我的代码段获取其部门
marc_s

1
GetGroups方法对我不起作用,我将新的主体上下文更改为使用构造函数的另一个重载,如下所示:PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain,“ 192.168.2.23”,“ domain \ user”,“ password” ); 这是完全合乎逻辑的,因为您并不总是通过活动目录身份验证登录。希望能对您
有所

2
这个答案很好。也可以将组迭代简化为:result.AddRange(user.GetAuthorizationGroups()。OfType <GroupPrincipal>()
tlbignerd 2015年

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之所以使用,是因为在一个非常大的广告中,我在200个组中的2个中有2个例外,因为某些SID不再可用。(该Translate()调用执行SID->名称转换。)


3
通过使用此技术而不是通过AD运行可以提高性能。谢谢!
菲利普(Philippe)

GetAuthorisationGroups()对我来说非常慢,即26,到目前为止,我发现的所有其他代码均未包含众所周知的标识符,例如Everyone,Domain Users等。您提供的代码在文学上是即时的,并且包含所有sid,是的,只有sid,但这就是我所需要的,包括著名的和自定义的sid!
蒂埃里(Thierry)'18

19

首先,GetAuthorizationGroups()是一个很棒的函数,但不幸的是有两个缺点:

  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;
}

太棒了!谢谢 我开始编写一些代码,并使用GetAuthorizationGroups,但由于花300ms-2.5s的时间来获取所有组而感到震惊。您的方法在20-30毫秒内完成。
基思

4
这似乎很有希望,但是它不能解析嵌套的组,例如,用户是组a的成员,而组a本身就是组x的成员。上面的代码将仅显示组a,而不显示组x。我通过tokenGroups使用了此方法:stackoverflow.com/a/4460658/602449
Robert Muehsig '16

看看罗伯特·穆西格(Robert Muehsig)的评论-这确实嵌套了小组,而且速度更快。唯一的缺点是它只会返回安全组而不是分发组
Nick Rubino

@bigjim无法使用GetAuthorizationGroups,因为它需要近6秒钟才能返回其数据,但是您提供的代码无法返回众所周知的组,例如Everyone,Domain Users等...,我需要这些。那里的所有内容似乎仅返回“自定义组”,而不是用户所属的每个组。
蒂埃里(Thierry)'18

11

在广告中,每个用户都有一个属性 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循环替换为生成组名列表而不是调试打印的循环即可。
乔尔·埃瑟顿

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授权访问组”
  2. 点击“高级”按钮
  3. 选择“ Windows授权访问组”,然后单击“查看”
  4. 选中“读取tokenGroupsGlobalAndUniversal”
  5. 找到所需的用户,然后将其添加到您从第一步创建(创建)的组中

1
如果您在Web应用程序中将内置帐户用于服务/应用程序池帐户,则可能会起作用。如果您将域帐户用作服务/应用程序池帐户,或在代码中模拟域帐户,则默认情况下该域帐户应具有读取权限,并且不会出现此问题。
vapcguy

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命名空间提供了两个组检索方法:

获取组返回对象的集合,这些组对象指定当前主体是其成员的组。

此重载方法仅返回主体直接为其成员的组;不执行递归搜索。

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)

如果要使用登录用户身份执行应用程序代码,则启用模拟。可以通过IIS或通过在web.config中添加以下元素来启用模拟。

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

如果启用了模拟,则应用程序将使用您的用户帐户中的权限执行。因此,如果登录的用户可以访问特定的网络资源,则只有他才能通过应用程序访问该资源。

感谢PRAGIM技术人员从他勤奋的视频中获得的信息

asp.net第87部分中的Windows身份验证:

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

但是模拟会在服务器上造成很多开销

允许某些网络组的用户的最佳解决方案是在Web配置中拒绝匿名 <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”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.