จะรับกลุ่มของผู้ใช้ใน 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;
}

@Tassisto: น่าเสียดายที่คุณสมบัตินั้นไม่สามารถใช้ได้โดยตรงในUserPrincipal- ดูคำตอบที่อัปเดตของฉันสำหรับวิธีการรับ
marc_s

ฉันต้องให้ชื่อผู้ใช้เพื่อรับค่าของเขตข้อมูลขาออก
Tassisto

@Tassito: ถ้าอย่างนั้น 1) สร้างบริบทโดเมน 2) ค้นหาผู้ใช้รายนั้นด้วยชื่อและ 3) ใช้ข้อมูลโค้ดของฉันเพื่อรับแผนก
marc_s

1
เมธอด GetGroups ไม่ได้ผลสำหรับฉันฉันเปลี่ยนบริบทหลักใหม่เพื่อใช้โอเวอร์โหลดอื่นของตัวสร้างดังนี้ PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "domain \ user", "password" ); เป็นตรรกะอย่างสมบูรณ์เนื่องจากคุณไม่ได้ล็อกอินผ่านการตรวจสอบสิทธิ์ไดเรกทอรีที่ใช้งานอยู่เสมอ หวังว่ามันจะช่วย
Omid S.

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 กลุ่มในโฆษณาที่ใหญ่มากเนื่องจาก SID บางตัวไม่สามารถใช้งานได้อีกต่อไป (การTranslate()โทรทำ SID -> การแปลงชื่อ)


3
การแสดงได้รับการปรับปรุงโดยใช้เทคนิคนี้แทนที่จะวิ่งผ่าน AD ขอบคุณ!
Philippe

GetAuthorisationGroups () ช้ามากสำหรับฉันคือ 26 และรหัสอื่น ๆ ทั้งหมดที่ฉันพบจนถึงตอนนี้ยังไม่มีตัวระบุที่รู้จักกันดีเช่นทุกคนผู้ใช้โดเมน ฯลฯ ... รหัสที่คุณให้นั้นใช้งานได้ทันทีตามตัวอักษรและรวมถึง sids ทั้งหมด ใช่เพียงด้านข้าง แต่นั่นคือสิ่งที่ฉันต้องการรวมถึงสิ่งที่เป็นที่รู้จักและกำหนดเองด้วย!
Thierry

19

ก่อนอื่น GetAuthorizationGroups () เป็นฟังก์ชันที่ยอดเยี่ยม แต่น่าเสียดายที่มีข้อเสีย 2 ประการ:

  1. ประสิทธิภาพไม่ดีโดยเฉพาะอย่างยิ่งใน บริษัท ขนาดใหญ่ที่มีผู้ใช้และกลุ่มจำนวนมาก มันดึงข้อมูลได้มากขึ้นจากนั้นคุณต้องการและเรียกใช้เซิร์ฟเวอร์สำหรับการวนซ้ำแต่ละครั้งในผลลัพธ์
  2. มันมีข้อบกพร่องที่อาจทำให้แอปพลิเคชันของคุณหยุดทำงาน 'สักวัน' เมื่อกลุ่มและผู้ใช้กำลังพัฒนา Microsoft รับรู้ปัญหาและเกี่ยวข้องกับ SID บางอย่าง ข้อผิดพลาดที่คุณจะได้รับคือ "เกิดข้อผิดพลาดขณะแจกแจงกลุ่ม"

ดังนั้นฉันจึงเขียนฟังก์ชันเล็ก ๆ เพื่อแทนที่ GetAuthorizationGroups () ด้วยประสิทธิภาพที่ดีขึ้นและปลอดภัยจากข้อผิดพลาด ทำการเรียก LDAP เพียง 1 ครั้งพร้อมกับแบบสอบถามโดยใช้ช่องที่จัดทำดัชนี สามารถขยายได้อย่างง่ายดายหากคุณต้องการคุณสมบัติมากกว่าเฉพาะชื่อกลุ่ม (คุณสมบัติ "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;
}

สุดยอด! Thanx. ฉันเริ่มเขียนโค้ดและใช้ GetAuthorizationGroups และรู้สึกตกใจที่ต้องใช้เวลา 300ms-2.5s เพื่อรับทุกกลุ่ม วิธีการของคุณเสร็จสิ้นใน 20-30 มิลลิวินาที
Keith

4
ดูเหมือนว่าจะมีแนวโน้มดี แต่ก็ไม่สามารถแก้ไขกลุ่มที่ซ้อนกันได้เช่นผู้ใช้เป็นสมาชิกของกลุ่ม a ซึ่งเป็นสมาชิกของกลุ่ม x โค้ดด้านบนจะแสดงกลุ่ม a แต่ไม่ใช่กลุ่ม x ฉันใช้วิธีนี้ผ่าน tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig

ดูความคิดเห็นของ Robert Muehsig ซึ่งเป็นกลุ่มที่ซ้อนกันและเร็วยิ่งขึ้น ข้อเสียเพียงอย่างเดียวเท่านั้นที่ส่งคืนกลุ่มความปลอดภัยไม่ใช่กลุ่มการแจกจ่าย
Nick Rubino

@bigjim ไม่สามารถใช้ GetAuthorizationGroups ได้เนื่องจากใช้เวลาเกือบ 6 วินาทีในการส่งคืนข้อมูล แต่รหัสที่คุณให้มาไม่ส่งคืนกลุ่มที่รู้จักกันดีเช่นทุกคนผู้ใช้โดเมน ฯลฯ ... และฉันจำเป็นต้องมีสิ่งเหล่านี้ ดูเหมือนว่าทุกอย่างจะส่งคืนเฉพาะ "กลุ่มที่กำหนดเอง" และไม่ใช่ทุกกลุ่มที่ผู้ใช้เป็นสมาชิก
Thierry

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 ไม่มีปัญหานี้
Andy

1

ในกรณีของฉันวิธีเดียวที่ฉันสามารถใช้ GetGroups () ต่อไปโดยไม่มี expcetion คือการเพิ่มผู้ใช้ (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 Authorization Access Group" และคลิกที่ "View"
  4. เลือก "อ่าน tokenGroupsGlobalAndUniversal"
  5. ค้นหาผู้ใช้ที่ต้องการและเพิ่มในกลุ่มที่คุณสร้าง (ถ่าย) ตั้งแต่ขั้นตอนแรก

1
สิ่งนี้จะเกิดขึ้นหากคุณใช้บัญชีในตัวสำหรับบัญชีบริการ / แอปพูลในเว็บแอปพลิเคชันของคุณ หากคุณใช้บัญชีโดเมนเป็นบัญชีบริการ / แอปพูลหรือแอบอ้างเป็นบัญชีโดเมนภายในรหัสบัญชีนั้นควรมีสิทธิ์ในการอ่านโดยค่าเริ่มต้นและไม่มีปัญหานี้
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.AccountManagementnamespace ให้สองวิธีการดึงกลุ่ม:

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"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.