如何检查用户是否属于AD组和嵌套组?

5
我有一个使用Windows身份验证和实现自己的RoleProvider的ASP.NET 3.5应用程序。
问题是,我们想将对一组页面的访问限制在几千个用户之内,而不是一个一个地输入它们,我们发现它们属于一个AD组。
答案很简单,如果我们正在检查成员身份的常见组是它的直接成员,则问题就解决了,但我遇到的问题是,如果该组是其他组的成员,然后随后成为另一组的成员,则我的代码总是返回false。
例如:假设我们想检查用户是否是组E的成员,但是用户不是*E*的直接成员,她是“A”的成员,后者是“B”的成员,后者确实是E的成员,因此用户是*E*的成员
我们拥有的其中一种解决方案非常慢,尽管它可以给出正确的答案。
using (var context = new PrincipalContext(ContextType.Domain))
  {
    using (var group = GroupPrincipal.FindByIdentity(context, IdentityType.Name, "DL-COOL-USERS"))
    {
      var users = group.GetMembers(true); // recursively enumerate

      return users.Any(a => a.Name == "userName");
    }
  }

使用.NET 3.5 System.DirectoryServices.AccountManagement的原始解决方案是我试图让它工作的,当用户直接成为目标组的成员时,它确实可以工作,如下所示:

public bool IsUserInGroup(string userName, string groupName)
{
  var cxt = new PrincipalContext(ContextType.Domain, "DOMAIN");
  var user = UserPrincipal.FindByIdentity(cxt, IdentityType.SamAccountName, userName);

 if (user == null)
  {       
    return false;
  }


  var group = GroupPrincipal.FindByIdentity(cxt, groupName);

  if (group == null)
  {        
    return false;
  }

  return user.IsMemberOf(group);
}

总之,即使分组嵌套多层,我们仍需要检查成员身份。

非常感谢!


不幸的是,我认为你的慢版本是最好的选择。如果有价值的话,将AD升级到2008(在我们的情况下是R2)功能级别可以大大提高此操作的性能...但这只是一个罕见的情况,你可能无法引起这种升级,我们只是碰巧正在进行中。 - Nick Craver
谢谢Nick!我们的“森林”很庞大,例如超过10万名员工,所以你可以想象出现的问题。 - Intrigue
2个回答

1

解决方案1:使用web.config限制访问

由于您正在使用ASP.NET,限制访问的更简单的解决方案是在web.config中进行设置,示例如下:

<configuration>
  <location path="protected/url/path">
    <system.web>
      <authorization>
        <allow roles="DOMAIN\group"/>
        <deny users="*"/>
      </authorization>
    </system.web>
  </location>
</configuration>

更多信息请参见:http://msdn.microsoft.com/en-us/library/wce3kxhd.aspxhttp://msdn.microsoft.com/en-us/library/b6x6shw7.aspx

解决方案2:IsUserInGroup的更快实现

这是我在.NET 2.0应用程序中使用的代码,在类似于您的大型域中运行良好。

static bool IsUserInGroup(DirectoryEntry user, string groupName)
{
    user.RefreshCache(new string[] {"tokenGroups"});
    for (int i = 0; i < user.Properties["tokenGroups"].Count; i++) {
        SecurityIdentifier sid = new SecurityIdentifier((byte[]) user.Properties["tokenGroups"][i], 0);
        try {
            NTuser nt = (NTuser) sid.Translate(typeof(NTuser));
            if (nt.Value.ToLower().EndsWith("\\" + groupName.ToLower())) {
                return true;
            }
        } catch (IdentityNotMappedException) {
            continue;
        }
    }

    return false;
}

Amry,感谢您的回答。但是,解决方案1并没有奏效,因为我正在实现自己的RoleProvider。我认为如果我们使用WindowsTokenRoleProvider,则您的解决方案将奏效。我尝试了解决方案2,但它没有奏效。无论如何,感谢您花费的时间。 - Intrigue
@elsharpo:您能否再试一次,使用我提供的解决方案2。我误复制了错误的代码,正确的应该使用nt.Value.ToLower().EndsWith() 而不是之前我提供的nt.Value.ToLower().Equals()。另外,我可以知道您实现自己的RoleProvider的原因是什么吗?因为在我看来,您正在检查AD和内置的提供程序应该可以正常工作。 - Amry
嗨Amry。很抱歉回复你晚了,但是它没有起作用。不过我很高兴地说,我们通过直接查看AD并查看员工的职位找到了解决方案,速度非常快。 - Intrigue
很遗憾它对你没用,但对我来说却有效。可能是AD基础设施设置中有些不同的地方。无论如何,很高兴你找到了可行的解决方案。 - Amry

1

好的,我必须在这个问题上跳出传统思维方式,幸运的是通过研究AD,我们发现所有需要查看页面的3000多人都共享一个自定义AD属性中的关键字。所以我只需要从AD加载对象并查找它。最终客户非常满意!目前这似乎是一个有点“肮脏”的解决方案,但一如既往地完美运作,我们必须继续前进到其他事情上!

目前我们正在进行试点计划,并希望任何错误都能够被通知并上报,以便我们可以采取行动。

public static bool IsEmployeeCoolEnoughToSeePages(string userName)
{
  if (string.IsNullOrEmpty(userName))
  {
    throw new ArgumentNullException("Current user's Username cannot be null");
  }

  using (var searcher = new DirectorySearcher(("LDAP://YOURDOMAIN.com")))
  {
    searcher.Filter = string.Format("(&(objectCategory=user)(samAccountName={0}))", ariseUserName);
    SearchResultCollection searchResultCollection = searcher.FindAll();

    if (searchResultCollection.Count == 1)
    {
      var values = searchResultCollection[0].Properties["title"];

      if (values.Count == 1)
      {
        return values[0].ToString().StartsWith("_COOLNES_");
      }
    }

    return false;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接