65.9K
CodeProject 正在变化。 阅读更多。
Home

LDAP 搜索实用程序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2017年10月25日

CPOL

8分钟阅读

viewsIcon

38689

downloadIcon

1343

单个 C# 类文件,可以放入任何项目中并立即使用。

LDAP Search Utility

引言

这是一个易于使用的、可编程的 LDAP 搜索实用类,开箱即用。它使用了无服务器绑定技术和匿名安全上下文。它适用于大多数微软风格的活动目录。搜索活动目录以查找

  • 用户
  • 计算机
  • 打印机

该类灵活支持以下功能(但不限于此)

  • 验证用户的组成员身份
  • 列出组的成员
  • 检查用户活动目录账户的锁定状态
  • 用户上次登录或注销的时间?

此实用工具可以返回特定属性或所有属性。目录搜索过滤器实现针对的是类别而非(参见 objectCategory vs. objectClass)。此实用工具充当 System.DirectoryServices.DirectorySearcher 的包装器。

背景

此实用工具并非万能的解决方案,因为实现 LDAP 搜索的方法有很多。在 codeproject.com 上快速搜索 LDAP 文章就会证明这一点。此实用工具安全上下文实现的匿名性允许以简单灵活的方式搜索 LDAP 信息。我想创建一种更轻松地检索 LDAP 信息的方法,并能在任何地方使用它。

在一个最近的项目中,我需要将用户的职位(ldap 属性)与一个常量进行比较。如果职位包含该常量,则逻辑会相应地分支。另一个用例要求用户是某个活动目录组成员 - 不要与 active directory membership provider 类混淆。如果用户是给定活动目录组成员,则逻辑会相应地分支。这些类型的搜索可以通过此实用类更轻松地实现。此外,此实用工具是一个独立的类,可以即插即用,立即在任何项目中启用。

Using the Code

无服务器绑定

无服务器绑定技术 提供了极大的灵活性。在 LDAP 3.0 中引入的 RootDSE 被定义为目录服务器上目录数据树的根。rootDSE 不属于任何命名空间。rootDSE 的目的是提供有关目录服务器的数据。因此,无需了解 LDAP 地址,它通常适用于大多数微软活动目录。RootDSE 代表 Root DSA (Directory Service Agent) Specific Entry,它是 LDAP 服务器的根。此条目是一个伪对象,这意味着它是树根处的未命名条目。此实用工具类可以即插即用,立即在任何项目中启用。以下是构造函数的源代码:

public LDAPSearch(){
    if (IsLDAPAvailable()) //throws an exception if the directory is not available
    {
        DirectoryEntry root = new DirectoryEntry("LDAP://RootDSE");
        //root.Properties contain a list of properties 
        //that describe the directory server.  Eg. "defaultNamingContext"
        root = new DirectoryEntry
               ("LDAP://" + root.Properties["defaultNamingContext"][0]);
        this.DirectorySearcher = new DirectorySearcher(root);
    }
} 

LDAP 搜索的本质有返回多个值的倾向。此实用工具假定可以返回多个结果集。返回对象被整齐地打包成一个复杂字典。对于给定的搜索,需要传入所需的返回属性。这些属性会被添加到 DirectorySearcher.PropetiesToLoad 集合中。我使用一个 Enum 来使实现更容易使用,它包含在 LDAPSearch.DirectoryProperty 中。每个 LDAPSearch.DirectoryProperty enum 都包含一个描述属性,该属性与目录中的实际属性匹配。开箱即用,源代码包含一组有限的常用属性。目录管理员通常会为各种用例添加自定义属性。您需要扩展 LDAPSearch.DirectoryProperty enum 集合以满足您的需求。有关微软导向目录属性的完整列表,请参阅 MSDN 上的索引属性

构造函数

//.ctor
var ldap = new LDAPSearch(); 

//optionally add some target properties to return from the result
var props = new List<LDAPSearch.DirectoryProperty>();
props.Add(LDAPSearch.DirectoryProperty.SAMACCOUNTNAME);

//invoke the search method
var toReturn = ldap.LDAPSearch([SEARCH TARGET], [SEARCH BY], 
    ["SEARCH KEY"],[0..N PROPERTIES TO SEARCH FOR], [BEGIN RANGE], [END RANGE]);
  • [搜索目标]:enum LDAPSearch.SearchTarget
  • [搜索依据]:enum LDAPSearch.SearchBy
  • ["搜索键"]:这是被搜索的值
  • [0..N 个搜索属性]:一个 LDAPSearch.DirectoryProperty enum 的集合
  • [开始范围]:可选,可以省略
  • [结束范围]:可选,可以省略
    注意:[开始范围]、[结束范围] 用于 [搜索目标] = LDAPSearch.SearchTarget.GROUP 时,是可选的。

返回对象 - Dictionary<int, List<KeyValuePair<string, List<string>>>>

鉴于 LDAP 目录搜索的性质,默认情况下,在使用 System.DirectoryServices.DirectorySearcher.FindAll() 时,结果被埋在 System.DirectoryServices.SearchResultCollection 对象中(参见 MSDN)。也可能返回多个结果集。例如,按员工 ID 搜索用户时,可能有关联的多个活动目录登录帐户。有些人可能会发现从该集合中提取值很困难。此实用工具通过将结果转储到一个索引的复杂字典中来简化此操作。通过键一行代码检索值使过程非常简单和干净。此外,使用 LINQ/Lambda 遍历字典也更方便。

注意[0..N 个搜索属性] 参数是一个 enum 集合,类型为 LDAPSearch.DirectoryProperty,其描述属性与活动目录中包含的可用属性的拼写完全匹配。并非所有活动目录都包含相同的属性。我包含了一组基本的 LDAPSearch.DirectoryProperty enums,带有最常用的描述属性。您可以根据需要进行扩展。

用户搜索

该实用工具允许通过多种方式在活动目录中搜索用户。
注意props 参数。props 是一个 LDAPSearch.DirectoryProperty enum 集合,包含 0 个或多个所需返回的属性。

//Load up some properties to return
props.Add(LDAPSearch.DirectoryProperty.LOCKOUTTIME);
props.Add(LDAPSearch.DirectoryProperty.LASTLOGOFF);
props.Add(LDAPSearch.DirectoryProperty.LASTLOGON);
props.Add(LDAPSearch.DirectoryProperty.SAMACCOUNTNAME);
props.Add(LDAPSearch.DirectoryProperty.USERACCOUNTCONTROL);

//By Email
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.USER, 
               LDAPSearch.SearchBy.EMAIL, "mike_direnzo@hotmail.com", props); 

//By Active Directory ID (VH)
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.USER, 
               LDAPSearch.SearchBy.AD_LOGIN_ID, "OU812",  props); 

//By CNAME
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.USER, 
               LDAPSearch.SearchBy.CNNAME, "Mike Direnzo", props);

//By Employee ID
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.USER, 
               LDAPSearch.SearchBy.EID, "XXXXXXX", props); 

//Wild Card - returns all EIDs that start with 11
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.USER, 
               LDAPSearch.SearchBy.EID, "11*", props); 

组搜索

通过将搜索目标更改为:LDAPSearch.SearchTarget.GROUP,该实用工具允许搜索活动目录组。搜索结果将默认返回一个名为 member;range=0-1499 的特殊属性。它包含属于该组的所有活动目录用户和/或实体。在某些活动目录服务器控制器配置中,返回的最大对象数为 1500。这由range 属性控制。此实用工具抽象了实现方面,只需提供一个范围 - 并且它是可选的。如果不提供,则默认为 0-1499。member;range=0-1499 属性将包含所有活动目录成员,并且可能数量庞大。因此,可以通过多次调用搜索方法并提供后续连续的范围来分页给定活动目录组的整个成员。
注意props 参数。props 是一个 enum 集合,包含 0 个或多个所需返回的属性。

props.Add(LDAPSearch.DirectoryProperty.MAIL);

//omitting range search will return 0-1499 results 
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.GROUP, 
               LDAPSearch.SearchBy.AD_LOGIN_ID,"AD_GROUP_1",props);

//using a range search
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.GROUP, 
               LDAPSearch.SearchBy.AD_LOGIN_ID,"AD_GROUP_1",props, 100, 199);
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.GROUP, 
               LDAPSearch.SearchBy.AD_LOGIN_ID,"AD_GROUP_1",props, 200, 299);

计算机名称搜索

通过将搜索目标更改为:LDAPSearch.SearchTarget.COMPUTER,该实用工具允许搜索计算机名称。如果您需要查看计算机属于哪个 OU,这会很有用。例如,活动目录中的组策略是按 OU 和其所在的 LDAP 路径应用的。当一个人知道计算机所在的精确 OU 时,故障排除会容易得多。显然,这也可以应用于用户、组、打印机等。
注意props 参数。props 是一个 LDAPSearch.DirectoryProperty enum 集合,包含 0 个或多个所需返回的属性。

props.Add(LDAPSearch.DirectoryProperty.DISTINGUISHEDNAME);
props.Add(LDAPSearch.DirectoryProperty.NAME);

//COMP_1001001 (Rush SOS)
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.COMPUTER, 
               LDAPSearch.SearchBy.COMPUTERNAME,"COMP_1001001",props);

打印机名称搜索

通过将搜索目标更改为:LDAPSearch.SearchTarget.PRINTER,该实用工具允许搜索打印机。如果您需要查看打印机属于哪个 OU,这会很有用。例如,活动目录中的组策略是按 OU 和其所在的 LDAP 路径应用的。当一个人知道打印机所在的精确 OU 时,故障排除会容易得多。
注意props 参数。props 是一个 LDAPSearch.DirectoryProperty enum 集合,包含 0 个或多个所需返回的属性。

props.Add(LDAPSearch.DirectoryProperty.DISTINGUISHEDNAME);
props.Add(LDAPSearch.DirectoryProperty.NAME);

//wild card
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.PRINTER, 
               LDAPSearch.SearchBy.PRINTERNAME,"PS*",props);

//specific
var toReturn = ldap.LDAPSearch(LDAPSearch.SearchTarget.PRINTER, 
               LDAPSearch.SearchBy.PRINTERNAME,"PS100100",props);

检索值

此实用工具将目录搜索结果转储到一个复杂对象:Dictionary<int, List<KeyValuePair<string, List<string>>>>。从结果中检索项只需一行代码。当然,唯一的依赖项是,当从结果集中检索项时,该项需要已在 props 集合中(一个 enum 集合,类型为 LDAPSearch.DirectoryProperty)。否则会抛出异常。

该实用工具公开了一个具有两个重载的方法

  • var stringValue = ldap.GetValue(LDAPSearch.DirectoryProperty.MEMBEROF, toReturn) 通过键返回单个值。
  • var booleanValue = ldap.GetValue(LDAPSearch.DirectoryProperty.MEMBEROF, toReturn, "AD_GROUP_1")
    注意toReturn 包含搜索结果。
    此重载返回一个布尔值,用于检查 LDAPSearch.DirectoryProperty.MEMBEROF 属性(类型为 String 的集合)中是否存在组名值。毫无疑问,这可以在未来版本中进行扩展或重构。
//User 
var isUserLogonAccountLocked = ldap.GetValue(LDAPSearch.DirectoryProperty.LOCKOUTTIME, 
                               toReturn); //returns 0 or 1
var message = string.Format("Account: {0} locked: {1}",
        ldap.GetValue(LDAPSearch.DirectoryProperty.SAMACCOUNTNAME, toReturn),
		(System.Convert.ToInt32(isUserLogonAccountLocked) > 0));

//Group
var t = ldap.GetValue(LDAPSearch.DirectoryProperty.MEMBEROF,
                      toReturn, "AD_GROUP_1"); //returns bool

//Computer
var computerName = ldap.GetValue
                   (LDAPSearch.DirectoryProperty.DISTINGUISHEDNAME, toReturn);

//Printer
var printerName = ldap.GetValue
                  (LDAPSearch.DirectoryProperty.DISTINGUISHEDNAME, toReturn);

兼容性问题

您是否曾经下载过一个代码片段或项目,却发现它与您的 .NET Framework 或 Visual Studio 版本不兼容?

.NET 版本兼容性

该实用工具无需修改即可使用 .NET Framework 版本 4.0 - 4.6.x。有一个方法支持命名参数和可选参数,这些参数在 .NET 4.0 中引入。我还使用了一个匿名委托来返回搜索结果(在 .NET 3.0 中引入)。据我所知,在撰写本文时,由于未包含 System.DirectoryServices 类,因此它尚不兼容 .NET CORE 1.0 或 .NET 2.0。这是使用 Visual Studio 2015 .NET Framework 4.6 构建并成功实现的。

已知兼容的活动目录版本

AD DS 版本
Windows Server 2008 30
Windows Server 2008 R2 31
Windows Server 2012 31

历史

  • 2017 年 10 月 21 日:初始版本
© . All rights reserved.