有没有办法让 UserPrincipal.GetGroups() 和 UserPrincipal.GetAuthorizationGroups() 调用使用 LDAPS(端口 636)而不是 LDAP(端口 389)?

Is there a way to make UserPrincipal.GetGroups() and UserPrincipal.GetAuthorizationGroups() calls use LDAPS (port 636) instead of LDAP (port 389)?

我们正在为 Microsoft 的 3 月 AD 更新做准备,以仅允许使用 LDAPS 进行安全调用,在检查我们的 .Net 代码时,我发现出现了对 UserPrincipal.GetGroups() 和 UserPrincipal.GetAuthorizationGroups() 的调用使用 LDAP(端口 389)而不是 LDAPS(端口 636),即使 UserPrincipal 对象是使用在 LDAPS 上建立的 PrincipalContext 创建的,如下所示:

    // Explicitly using LDAPS (port 636)
    PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "our.corpdomain.com:636", "DC=our,DC=corpdomain,DC=com", ContextOptions.Negotiate);
    UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, "someuser");

    // These calls still use LDAP (port 389)
    var groups = userPrincipal.GetAuthorizationGroups();
    var groups2 = userPrincipal.GetGroups();

有谁知道为什么会发生这种情况,如果是这样,如何强制这些调用使用 LDAPS?如果不能强制,是否有任何解决方法?

这肯定是 .NET 代码中的错误,我会回答你的问题,但正如我在你的其他问题中提到的那样,三月更新不会 "only allow secure calls using LDAPS"。更新后,正常的 LDAP 端口 389 仍然可以使用。我没有看到任何证据表明他们曾计划禁用它。

但是如果你想确保它永远不会使用端口 389,你就不能不使用 UserPrincipal。直接用DirectoryEntry and/or DirectorySearcher,反正后台用的就是UserPrincipal。这不是 AccountManagement 命名空间中的 the first bug I've found

我写了一篇关于finding all of a user's groups的文章,里面有一些针对不同场景的示例代码。您必须修改任何创建新 DirectoryEntry 对象并指定端口 636 的情况,如下所示:

new DirectoryEntry("LDAP://example.com:636/CN=whatever,DC=example,DC=com")

如果愿意,您实际上可以省略域名(只需 :636 而不是 example.com:636)。

我在那篇文章中没有涉及的一个案例相当于GetAuthorizationGroups,即读取tokenGroups属性。这会为您提供组的 SID 列表,然后您可以查找该列表以找到组的名称。这是一个可以做到这一点的方法:

private static IEnumerable<string> GetTokenGroups(DirectoryEntry de) {
    var groupsFound = 0;

    //retrieve only the tokenGroups attribute from the user
    de.RefreshCache(new[] {"tokenGroups"});

    while (true) {
        var tokenGroups = de.Properties["tokenGroups"];
        foreach (byte[] groupSidByte in tokenGroups) {
            groupsFound++;
            var groupSid = new SecurityIdentifier(groupSidByte, 0);
            var groupDe = new DirectoryEntry($"LDAP://:{de.Options.PasswordPort}/<SID={groupSid}>");

            groupDe.RefreshCache(new[] {"cn"});
            yield return (string) groupDe.Properties["cn"].Value;
        }

        //AD only gives us 1000 or 1500 at a time (depending on the server version)
        //so if we've hit that, go see if there are more
        if (tokenGroups.Count != 1500 && tokenGroups.Count != 1000) break;

        try {
            de.RefreshCache(new[] {$"memberOf;range={groupsFound}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) break; //no more results

            throw;
        }
    }
}

这将使用您用于创建您传入的 DirectoryEntry 对象的任何端口。但是,如果您的环境中有多个域,这将中断。如果您想始终使用端口 636,那么在这种情况下事情会变得复杂。