8

Microsoft 有一篇通用知识库文章 ( Q316748 ) 描述了如何使用DirectoryEntry对象对 Active Directory 进行身份验证。在他们的示例中,他们通过将域名和用户名连接到标准 NetBIOS 格式(“域\用户名”)并将其作为参数传递给目录条目构造函数来生成用户名值:

string domainAndUsername = domain + @"\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);

最近我们注意到,用户名的域部分被完全忽略了,在多个环境中我已经确认了这种行为。用户名和密码实际上正在被使用,因为当它们无效时身份验证失败,但可以为域名和身份验证通过提供任意值。乍一看,我认为这种格式适用于基于 WinNT 的目录访问,但域部分对于 LDAP 被忽略。

对谷歌的检查显示许多 LDAP 示例将“域\用户名”值传递给DirectoryEntry对象,所以我要么在我的配置中搞砸了一些东西,要么有很多人对知识库文章感到困惑。任何人都可以确认这是预期的行为或推荐一种接受“域\用户名”值并使用它们针对 Active Directory 进行身份验证的方法吗?

谢谢,

4

2 回答 2

18

简短回答:当构造函数的path参数DirectoryEntry包含无效域名时,DirectoryEntry对象将(在 forrest 中搜索无效域失败后)尝试通过删除username参数的域部分并尝试使用普通用户名进行连接来回退(sAMAccountName)。

长答案:如果参数中指定的域名username无效但用户存在于path参数中指定的域中,则用户将通过身份验证(通过使用回退)。但是,如果用户存在于 forrest 中的另一个域中,而不是参数中指定的域,则只有在包含参数的域部分并且正确path时,身份验证才会成功。username

在处理 DirectoryEntry 对象时,有四种不同的方式来指定用户名参数:

  • 专有名称(CN=用户名,CN=用户,DC=域,DC=本地)
  • NT 帐户名称(域\用户名)
  • 普通账户名/sAMAccountname(用户名)
  • 用户主体名称(通常为 username@domain.local)

让我用一个例子来说明:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.DirectoryServices;

namespace DirectoryTest
{
  class Program
  {

    private static Int32 counter = 1;

    static void Main(string[] args)
    {
      TestConnection();
    }

    private static void TestConnection()
    {
      String domainOne = "LDAP://DC=domain,DC=one";
      String domainOneName = "DOMAINONE";
      String domainOneUser = "onetest";
      String domainOnePass = "testingONE!";

      String domainTwo = "LDAP://DC=domain,DC=two";
      String domainTwoName = "DOMAINTWO";
      String domainTwoUser = "twotest";
      String domainTwoPass = "testingTWO!";

      String invalidDomain = "INVALIDDOMAIN";

      // 1) This works because it's the correct NT Account Name in the same domain:
      Connect(domainOne, domainOneName + "\\" + domainOneUser, domainOnePass);

      // 2) This works because username can be supplied without the domain part
      // (plain username = sAMAccountName):
      Connect(domainOne, domainOneUser, domainOnePass);

      // 3) This works because there's a fall back in DirectoryEntry to drop the domain part
      // and attempt connection using the plain username (sAMAccountName) in (in this case)
      // the forrest root domain:
      Connect(domainOne, invalidDomain + "\\" + domainOneUser, domainOnePass);

      // 4) This works because the forrest is searched for a domain matching domainTwoName:
      Connect(domainOne, domainTwoName + "\\" + domainTwoUser, domainTwoPass);

      // 5) This fails because domainTwoUser is not in the forrest root (domainOne)
      // and because no domain was specified other domains are not searched:
      Connect(domainOne, domainTwoUser, domainTwoPass);

      // 6) This fails as well because the fallback of dropping the domain name and using
      // the plain username fails (there's no domainTwoUser in domainOne):
      Connect(domainOne, invalidDomain + "\\" + domainTwoUser, domainTwoPass);

      // 7) This fails because there's no domainTwoUser in domainOneName:
      Connect(domainOne, domainOneName + "\\" + domainTwoUser, domainTwoPass);

      // 8) This works because there's a domainTwoUser in domainTwoName:
      Connect(domainTwo, domainTwoName + "\\" + domainTwoUser, domainTwoPass);

      // 9) This works because of the fallback to using plain username when connecting
      // to domainTwo with an invalid domain name but using domainTwoUser/Pass:
      Connect(domainTwo, invalidDomain + "\\" + domainTwoUser, domainTwoPass);
    }

    private static void Connect(String path, String username, String password)
    {
      Console.WriteLine(
        "{0}) Path: {1} User: {2} Pass: {3}",
        counter, path, username, password);
      DirectoryEntry de = new DirectoryEntry(path, username, password);
      try
      {
        de.RefreshCache();
        Console.WriteLine("{0} = {1}", username, "Autenticated");
      }
      catch (Exception ex)
      {
        Console.WriteLine("{0} ({1})", ex.Message, username);
      }
      Console.WriteLine();
      counter++;
    }
  }
}

在上面的示例中,domain.one 是 forrest 根域,domain.two 与 domain.one 位于同一 forrest 中(但自然是不同的树)。

因此,回答您的问题:如果用户不在我们要连接的域中并且在参数中未指定或指定无效域名,则身份验证将始终失败username

于 2009-10-16T09:30:41.027 回答
0

我有两个使用DirectoryEntry(_path, domainAndUsername, pwd);构造函数的应用程序,并且没有任何身份验证问题。每个应用程序都安装在不同的客户上,两者都具有非常(非常)大的域结构。

于 2009-10-15T23:20:58.077 回答