【发布时间】:2011-10-19 19:24:08
【问题描述】:
我正在编写一个基于 MVC (.NET 4.0) 的网站,该网站需要来自公司 LDAP 服务器的登录凭据。我的代码需要的是只允许属于某个组的用户。例如,我可能正在寻找属于“企业 IT”组的用户。我的凭据可能是“系统管理员”组的一部分,该组是“公司 IT”的子组。我正在使用表单身份验证。
我如何递归检查用户登录时属于哪个组?
【问题讨论】:
标签: c# asp.net-mvc-3 ldap
我正在编写一个基于 MVC (.NET 4.0) 的网站,该网站需要来自公司 LDAP 服务器的登录凭据。我的代码需要的是只允许属于某个组的用户。例如,我可能正在寻找属于“企业 IT”组的用户。我的凭据可能是“系统管理员”组的一部分,该组是“公司 IT”的子组。我正在使用表单身份验证。
我如何递归检查用户登录时属于哪个组?
【问题讨论】:
标签: c# asp.net-mvc-3 ldap
对于通过搜索此类查询来到这里的其他人,这是我在应用程序中的做法:
关键是1.2.840.113556.1.4.1941扩展搜索过滤器。由于此特定过滤器仅适用于 DN,因此我首先获取要检查的用户的 DN,然后查询组以查看此特定用户是否是链中任何组的成员。
internal const string UserNameSearchFilter = "(&(objectCategory=user)(objectClass=user)(|(userPrincipalName={0})(samAccountName={0})))";
internal const string MembershipFilter = "(&(objectCategory=group)(objectClass=group)(cn=MyGroup)(member:1.2.840.113556.1.4.1941:={0}))";
using (var de = new DirectoryEntry(AppSettings.LDAPRootContainer, AppSettings.AdminUser, AppSettings.AdminPassword, AuthenticationTypes.FastBind))
using (var ds = new DirectorySearcher(de) { Filter = string.Format(UserNameSearchFilter, username) })
{
ds.PropertiesToLoad.AddRange(new[] { "distinguishedName" });
var user = ds.FindOne();
if (user != null)
using (var gds = new DirectorySearcher(de) { PropertyNamesOnly = true, Filter = string.Format(MembershipFilter, user.Properties["distinguishedName"][0] as string) })
{
gds.PropertiesToLoad.AddRange(new[] { "objectGuid" });
return gds.FindOne() != null;
}
}
【讨论】:
我发现使用带有递归标志的GroupPrincipal.GetMembers 既快速又有效。
public bool IsMember(string groupName, string samAccountName)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, samAccountName))
using(GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName))
{
return group.GetMembers(true).OfType<Principal>().Any(u => u.SamAccountName.Equals(user.SamAccountName, StringComparison.InvariantCultureIgnoreCase));
}
}
【讨论】:
如果要检查特定用户的成员资格,请绑定到相关 AD 对象并检索 tokenGroups 属性。它包含二进制形式的所有直接和间接组成员身份 - 它是一个字节数组的数组。每个字节数组都可以传递给 SecurityIdentifier 类的构造函数,然后转换为 NTAccount,其中包含明文形式的组名称。
var sids = new IdentityReferenceCollection();
foreach (byte[] group in tokenGroups)
{
sids.Add(new SecurityIdentifier(group, 0));
}
var accounts = sids.Translate(typeof(NTAccount));
【讨论】:
这是一个完全不同的解决方案。经过测试并在我的域上工作。一些注意事项:您必须正确设置 DirectorySearcher.Filter。为您的 AD 层次结构添加多个 OU(以相反的顺序,自下而上)。另请注意,为了安全起见,我在“使用”语句中处理了一些对象,因为它们实现了 System.ComponentModel.Component,而后者又实现了 IDisposable……所以,安全总比抱歉好。
public bool IsUserMemberOfGroup(string groupName)
{
// CN is your distro group name. OU is the object(s) in your AD hierarchy. DC is for your domain and domain suffix (e.g., yourDomain.local)
string searchFilter = String.Format(@"(&(objectcategory=user)(sAMAccountName=markp)(memberof=CN={0},OU=System Admins,OU=USA,DC=yourDomain,DC=local))", groupName);
SearchResultCollection searchResult;
using (var dirEntry = new DirectoryEntry("LDAP://dc=yourDomain,dc=local"))
{
using (var dirSearch = new DirectorySearcher(dirEntry))
{
dirSearch.SearchScope = SearchScope.Subtree;
dirSearch.Filter = searchFilter;
searchResult = dirSearch.FindAll();
}
}
if (searchResult.Count <= 0 || searchResult == null) {
return false; // not in group
}
else {
return true; // in group
}
}
【讨论】: