通过VPN使用ldapsearch可以进行LDAP查询,但在C#(System.DirectoryServices.Protocols)中失败——是连接问题还是认证问题?

后端开发 2026-07-09

我正在排查一台通过VPN连接的机器上的LDAP连接问题。

让我提供一些我正在进行的背景信息:

  • 我的环境是一台通过VPN连接的远程机器,脚本预计在其他远程机器上运行。
  • LDAP服务器可以通过VPN远程访问,端口是389。
  • 这个脚本虽然是在.NET上开发,但它应该是跨操作系统的,且运行在.NET 10+。

在同一台机器上使用这个 ldapsearch CLI,我可以成功查询LDAP服务器:

ldapsearch -x -H ldap://<host>:389 -D "myDomain\MYUSER" -w MyPassword -b "baseDN" -LLL "(sAMAccountName=MYUSER)"

这会返回正确的结果(它能够连接并按预期获取用户)。

问题在于当我尝试通过 System.DirectoryServices.Protocols 使用相同的连接参数连接该LDAP服务时。我认为同样的查询会失败:

static LdapConnection OpenManualConnection(string ldapUri, NetworkCredential creds, string bindDn, string baseDn)
{
    var uri = new Uri(ldapUri);
    var port = 389;

    var addresses = Dns.GetHostAddresses(uri.Host)
    .Where(a => a.AddressFamily == AddressFamily.InterNetwork)
    .ToArray();

    var ldapId = new LdapDirectoryIdentifier(addresses[0].ToString(), port);

    Console.WriteLine($"Using manual LDAP connection settings: {ldapUri}:{port}, base DN: {baseDn}");

    var connection = new LdapConnection(ldapId, creds, AuthType.Basic)
    {
        SessionOptions =
        {
            ProtocolVersion = 3,
            SendTimeout = TimeSpan.FromSeconds(60)
        }
    };

    Console.WriteLine("Attempting LDAP bind with user: " + bindDn);

    var bindTask = Task.Run(connection.Bind);
    if (!bindTask.Wait(TimeSpan.FromSeconds(60)))
    {
        connection.Dispose();
        throw new TimeoutException("LDAP bind timed out after 60 seconds. The server may be unreachable.");
    }

    try
    {
        bindTask.GetAwaiter().GetResult();
    }
    catch (LdapException ldapEx)
    {
        connection.Dispose();
        // Mirror ldapsearch exit-code meanings for actionable messages
        throw new LdapException(ldapEx.ErrorCode, $"LDAP bind failed (code {ldapEx.ErrorCode}): {ldapEx.Message}{hint}");
    }

    return connection;
}

尽管在调试时所有参数都与 ldapsearch 中的一样,并且代码在测试时实际显示的消息是 Using manual LDAP connection settings: ...,但代码在创建 new LdapConnection 时就会停止。我也尝试过强制解析IPv4以便代码通过隧道,但没有成功。

在捕获错误时,我在控制台看到的错误是:

错误:Active Directory查询失败。LDAP服务器不可用

此外,我也尝试了不同的AuthType,但同样没有成功。

为什么 ldapsearch 在同一台机器上能成功,而 System.DirectoryServices.Protocols 会失败?在同一机器上,认证机制(简单绑定vs negotiate/NTLM/Kerberos)、TLS/StartTLS处理、引用跟踪(referral chasing) 或超时方面,是否存在差异?

在C#中需要什么配置才能匹配 ldapsearch 的行为?

对于 ldapsearch 与.NET LDAP客户端之间的差异,任何见解都将不胜感激。

这对我来说有点陌生,我也查阅了一些文档,但没有收获。

先行致谢

解决方案

这段代码量太大,且手动尝试DNS解析只会引发问题。主机名可能映射到多个IP,尤其是在使用VPN时。再者,将 Task.Run 这个阻塞方法“伪装”为异步会带来问题,导致代码变得过于复杂。

首先,不需要低级的LDAP连接。自2001年起,系统提供了通过 [System.DirectoryServices] 命名空间实现的高级目录搜索API,通过 [DirectorySearcher] 和 [DirectoryEntry] 类实现。起初,这是在PowerShell(基于.NET构建)中操作AD的唯一方式。相关的文章、博客和书籍不胜枚举。但经过二十五年的文档重组与迁移,连微软的文档也有点混乱。

文档中的 Using System.DirectoryServices 部分在这里,可以链接到快速入门和具体任务的示例。你会注意到目录结构有问题,因此需要通过链接进行导航。

你可以使用一个特定的LDAP URL绑定到服务器,甚至绑定到某个对象,正如Binding Strings文档所示:

using var ent = new DirectoryEntry("LDAP://server01.mydomain.whatever", userName, password);

usinh var userEntry = new DirectoryEntry("LDAP://fabrikam.com/CN=Jeff Smith,OU=Marketing,DC=fabrikam,DC=Com", userName, password);

一旦你有一个条目,你就可以查询它的属性

using var DirectoryEntry ent = new DirectoryEntry("LDAP://Fabrikam/CN=My Username,CN=Users,DC=Fabrikam,DC=com");
string name = ent.Properties["sn"].Value.ToString();
{
     Console.WriteLine(name);
}

DirectorySearcher 可用于 [搜索或筛选条目]。来自 [Setting Search Filters] 示例:

using DirectoryEntry entry = new DirectoryEntry("LDAP://CN=users,DC=fabrikam,DC=com");
using var DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(anr=test*))";

SearchResultCollection ResEnt = mySearcher.FindAll();

{
        // Handle results.
}

在2000年代,主要目标是桌面应用程序。async/await 要再等上12年才会出现。为了实现异步搜索,你需要设置 [DirectorySearcher.Asynchronous] 属性,并让你的示例列表框(eg listbox)绑定到结果。结果会在有结果时更新。类本身继承自 Component,因此可以直接放在窗体上。

// Bind to the users container.
DirectoryEntry entry = new DirectoryEntry("LDAP://CN=users,DC=fabrikam,DC=com");

// Create a DirectorySearcher object.
DirectorySearcher src = new DirectorySearcher(entry);

// Set the Asynchronous property to true.
src.Asynchronous = true;

// Use the FindAll method to get search results.
SearchResultCollection res = src.FindAll();
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章