如何在 Active Directory 中获取用户的组? (c#, asp.net)

Posted

技术标签:

【中文标题】如何在 Active Directory 中获取用户的组? (c#, asp.net)【英文标题】:How to get the groups of a user in 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;

【问题讨论】:

【参考方案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() 是一个很棒的函数,但不幸的是有两个缺点:

    性能很差,尤其是在有很多用户和组的大公司中。它会获取比您实际需要的更多的数据,并为结果中的每个循环迭代执行一次服务器调用 它包含的错误可能会导致您的应用程序在组和用户不断发展时“某天”停止工作。 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 使用了这个方法:***.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 中执行的步骤以使其正常工作:

    在 Active Directory 中创建一个组(或创建一个组)并在安全选项卡下添加“Windows 授权访问组” 点击“高级”按钮 选择“Windows授权访问组”并点击“查看” 勾选“读取 tokenGroupsGlobalAndUniversal” 找到所需的用户并添加到您从第一步创建(采取)的组中

【讨论】:

如果您在 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”

【讨论】:

以上是关于如何在 Active Directory 中获取用户的组? (c#, asp.net)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用C ++在Active Directory中获取maxpwdAge属性值?

给定 UserPrincipal 对象,如何从 Active Directory 中获取“公司”和“部门”?

LDAP:如何从 Active Directory 中获取所有用户和组

如何在 .NET Web 应用程序中为经过身份验证的用户获取 UPN,而不查询 Active Directory

您如何从 Azure Active Directory 中获取搭建的 Weather Forecast Api 将接受的访问令牌?

Active Directory - 如何检索用户的所有架构条目