【问题标题】:How to get the groups of a user in Active Directory? (c#, asp.net)如何在 Active Directory 中获取用户的组? (c#, asp.net)
【发布时间】:2011-07-15 15:55:47
【问题描述】:

我使用此代码获取当前用户的组。但我想手动给用户,然后得到他的组。我该怎么做?

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

【问题讨论】:

    标签: c# asp.net active-directory


    【解决方案1】:

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

    在此处阅读所有相关信息:Managing Directory Security Principals in the .NET Framework 3.5

    更新: 很遗憾,较早的 MSDN 杂志文章不再在线 - 您需要来自 Microsoft 的 download the CHM for the January 2008 MSDN magazine 并阅读其中的文章。

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

    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: 将这两个 sn-ps 代码放在一起似乎应该不会太难......但是没关系 - 就这样吧:

    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 上使用 - 请参阅我的更新答案以了解如何获取它。
    • 我需要提供用户名才能获取其部门字段的值
    • @Tassito:那么 1) 创建域上下文,2) 通过用户名找到该用户,3) 使用我的代码 sn-p 获取其部门
    • GetGroups 方法对我不起作用,我更改了新的主体上下文以使用构造函数的另一个重载,如下所示: PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain, "192.168.2.23","域\用户","密码");这是完全合乎逻辑的,因为您并不总是通过活动目录身份验证登录。希望对你有帮助
    • 这个答案非常好。也可以将组迭代简化为: result.AddRange(user.GetAuthorizationGroups().OfType()
    【解决方案2】:

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

    【讨论】:

    • 通过使用这种技术而不是通过 AD 运行,性能得到了改进。谢谢!
    • GetAuthorisationGroups() 对我来说超级慢,即 26 并且到目前为止我发现的所有其他代码都不包括众所周知的标识符,例如每个人、域用户等...您提供的代码从字面上看是即时的,包括所有 sid,是的,只有 sid,但这就是我需要的,包括众所周知的和自定义的!
    【解决方案3】:

    首先,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,但很震惊它需要 300 毫秒到 2.5 秒才能获取所有组。您的方法在 20-30 毫秒内完成。
    • 这看起来很有希望,但它不能解决嵌套组,例如用户是组 a 的成员,组 a 本身也是组 x 的成员。上面的代码将只显示组 a,而不是组 x。我通过 tokenGroups 使用了这个方法:stackoverflow.com/a/4460658/602449
    • 看看 Robert Muehsig 的评论 - 这确实是嵌套组,而且速度更快。唯一的缺点是它只返回安全组而不是通讯组
    • @bigjim 无法使用 GetAuthorizationGroups,因为它需要将近 6 秒才能返回其数据,但您提供的代码不会返回众所周知的组,例如每个人、域用户等......我需要有这些。那里的一切似乎只返回“自定义组”,而不是用户所属的每个组。
    【解决方案4】:

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

    【讨论】:

    • @Tassisto:是的,他理解你。上面的代码 sn-p 将完全按照您的喜好进行。只需将最终的 foreach 循环替换为生成组名列表的循环,而不是调试打印。
    • 它将无法列出用户的主要组(通常是域用户)。您必须返回并单独查询该信息。 GetAuthorizationGroups 没有这个问题。
    【解决方案5】:

    我的解决方案:

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

    【讨论】:

      【解决方案6】:

      在我的情况下,我可以毫无意外地继续使用 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. 找到所需的用户并添加到您从第一步创建(采取)的组中

      【讨论】:

      • 如果您在 Web 应用程序中使用内置帐户作为服务/应用程序池帐户,这可能会发挥作用。如果你使用域账号作为服务/应用池账号,或者在代码中冒充域账号,默认应该有读取权限,不会出现这个问题。
      【解决方案7】:

      这对我有用

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

      【讨论】:

        【解决方案8】:

        答案取决于您要检索的组类型。 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();
        

        【讨论】:

          【解决方案9】:

          如果 Translate 在本地工作但不能远程工作,则 e.i group.Translate(typeof(NTAccount)

          如果您希望使用 LOGGED IN USER 身份执行应用程序代码,请启用模拟。可以通过 IIS 或通过在 web.config 中添加以下元素来启用模拟。

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

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

          感谢 PRAGIM 技术从他的勤奋视频中提供这些信息

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

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

          但是模拟会在服务器上产生大量开销

          允许某些网络组的用户的最佳解决方案是在网络配置中拒绝匿名 &lt;authorization&gt;&lt;deny users="?"/&gt;&lt;authentication mode="Windows"/&gt;

          在你的代码后面,最好在 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”

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-24
            • 1970-01-01
            • 1970-01-01
            • 2018-06-26
            • 1970-01-01
            相关资源
            最近更新 更多