[我们正在为Microsoft的三月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?如果不能强迫他们,是否有解决方法?
但是,如果要确保它永远不会使用端口389,就不会使用UserPrincipal
。直接使用DirectoryEntry
和/或DirectorySearcher
,无论如何,这都是UserPrincipal
在后台使用的内容。这不是the first bug I've found名称空间中的AccountManagement
。
我写了一篇有关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,在这种情况下事情可能会变得复杂。