UserPrincipals.GetAuthorizationGroups枚举组时发生错误(1301)。升级到Server 2012域控制器后

时间:2013-06-10 15:44:44

标签: c# active-directory windows-server-2012 domaincontroller

研究

Similar Issue with workaround, but not actual solution to existing problem

Similar issue pointing to Microsoft End Point update as culprit

以上链接最适合我的问题,我也查看了Stack Overflow在创建这篇文章时列出的每个类似问题,只有上面引用的问题符合我的问题。

背景

我一直在使用UserPrincipal.GetAuthorizationGroups获取在C#.NET 4.0 Web表单站点上的Server 2008 R2上运行IIS 7.5的特定页面访问权限2年半。 2013年5月15日,我们删除了运行Server 2008(而不是r2)的主域控制器,并将其替换为Server 2012域控制器。第二天,我们开始收到下面列出的例外情况。

我使用Principal Context进行表单身份验证。用户名/通过握手成功并且正确设置了auth cookie,但是同时调用UserPrincipal.GetAuthorizationGroups的主要上下文调用也会间歇性地失败。我们已经解决了Server 2012域控制器中出现的一些BPA问题,但这还没有解决问题。我还建立了一个在两个独立服务器上运行的cron。虽然它们运行相同的代码库,但两个服务器将在不同的时间以组SID解析失败。 (开发环境和生产环境)。

该问题在Web服务器重启时暂时解决,并且在开发服务器上它将在12小时不能运行后自行解决。生产服务器通常会在重新启动之前停止正常运行,而不会自行解决。

此时我正在尝试优化网络中特定域控制器的cron以及新DC,并使用当前未能产生更多目标异常时间的标准LDAP查询。到目前为止,我们在一台Web服务器上发现,它失败的日子没有模式,但它将在大约12小时内恢复。最新结果显示,在上午8点至晚上8点之间,集团SID解决方案失败,然后恢复,几天后它将在晚上8点失败并在早上8点恢复,然后再运行12小时并再次失败。我们希望看看它是否只是一个特定的服务器通信问题,或者看它是否是整个域控制器集。

例外:

Exception information: 
Exception type: PrincipalOperationException 
Exception message: An error (1301) occurred while enumerating the groups.  
The group's SID could not be resolved.
at System.DirectoryServices.AccountManagement.SidList.TranslateSids(String target, IntPtr[] pSids)
at System.DirectoryServices.AccountManagement.SidList..ctor(SID_AND_ATTR[] sidAndAttr)
at System.DirectoryServices.AccountManagement.AuthZSet..ctor(Byte[] userSid, NetCred credentials, ContextOptions contextOptions, String flatUserAuthority, StoreCtx userStoreCtx, Object userCtxBase)
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOfAZ(Principal p)
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroups()

问题:

鉴于以上信息,有没有人知道为什么退出Windows Server 2008(而不是r2)并实施新的Server 2012 DC会导致UserPrincipal.GetAuthorizationGroups失败并出现1301 SID解析错误? 关于消除可能原因的想法也将受到赞赏。

免责声明:

这是我在Stack Overflow上的第一篇文章,我经常在这里研究,但直到现在才加入讨论。请原谅我,如果我应该在其他地方发布,并在发布之前随时指出更好的步骤。

更新13-JUN-2013:

6月12日,我解决了未处理问题的可能性。 时间框架太短,无法确定调整后的代码是否已修复问题,但我会继续更新,因为我们正努力达成一项解决方案,以便运气好的时候,有人可以伸出援助之手。

原始代码

    public bool isGroupMember(string userName, ArrayList groupList)
    {
        bool valid = false;

            PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain_server + ".domain.org:636", null, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);

            // find the user in the identity store
            UserPrincipal user =
                UserPrincipal.FindByIdentity(
                    ctx,
                    userName);

            // get the groups for the user principal and
            // store the results in a PrincipalSearchResult object
            PrincipalSearchResult<Principal> groups =
                user.GetAuthorizationGroups();

            // display the names of the groups to which the
            // user belongs
            foreach (Principal group in groups)
            {
                foreach (string groupName in groupList)
                {
                    if (group.ToString() == groupName)
                    {
                        valid = true;
                    }
                }

            }
        return valid;
    }

更新代码

        public bool isGroupMember(string userName, ArrayList groupList, string domain_server)
        {
        bool valid = false;

            try
            {

                using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain_server + ".domain.org:636", null, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
                {

                    // find the user in the identity store
                    UserPrincipal user =
                        UserPrincipal.FindByIdentity(
                            ctx,
                            userName);

                    try
                    {
                        // get the groups for the user principal and
                        // store the results in a PrincipalSearchResult object
                        using (PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups())
                        {
                            // display the names of the groups to which the
                            // user belongs

                            foreach (Principal group in groups)
                            {
                                foreach (string groupName in groupList)
                                {

                                    if (group.ToString() == groupName)
                                    {
                                        valid = true;
                                    }
                                }

                                group.Dispose();

                            }
                        }//end using-2
                    }
                    catch
                    {
                        log_gen("arbitrary info");
                        return false;
                    }
                }//end using-1
            }
            catch
            {
                log_gen("arbitrary info");
                return false;
            }

        return valid;

    }

10 个答案:

答案 0 :(得分:18)

我刚遇到同样的问题,我设法追踪的信息可能会有所帮助;如上所述,我们已经看到了域控制器运行Server 2012的问题 - 首先是客户部署,然后在我们自己的网络上复制。

经过一些实验,我们发现我们的代码在Server 2012上运行正常,但在客户端系统运行Server 2008时遇到了1301错误代码。关于发生了什么的关键信息可以在这里找到:

MS blog translated from German

下面链接中提到的修补程序修复了我们测试系统上的问题

SID S-1-18-1 and SID S-1-18-2 can't be mapped

希望这对某人有帮助!正如许多人已经注意到这种方法调用似乎相当脆弱,我们可能会在遇到其他问题之前考虑实施一些替代方法。

加里

答案 1 :(得分:5)

这是我的解决方案。它似乎一直很好。因为在迭代集合时会出现问题,所以我在迭代时使用不同的方法来处理异常而不会阻塞实际的迭代:

private string[] GetUserRoles(string Username)
{    
    List<string> roles = new List<string>();
    try
    {
        string domain = Username.Contains("\\") ? Username.Substring(0, Username.IndexOf("\\")) : string.Empty;
        string username = Username.Contains("\\") ? Username.Substring(Username.LastIndexOf("\\") + 1) : Username;
        if (!string.IsNullOrEmpty(domain) && !string.IsNullOrEmpty(username))
        {
            PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domain);
            UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, username);
            if (user != null)
            {
                PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
                int count = groups.Count();
                for (int i = 0; i < count; i++)
                {
                    IEnumerable<Principal> principalCollection = groups.Skip(i).Take(1);
                    Principal principal = null;
                    try
                    {
                        principal = principalCollection.FirstOrDefault();
                    }
                    catch (Exception e)
                    {
                        //Error handling...
                        //Known exception - sometimes AD can't query a particular group, requires server hotfix?
                        //http://support.microsoft.com/kb/2830145
                    }

                    if (principal!=null && principal is GroupPrincipal)
                    {
                        GroupPrincipal groupPrincipal = (GroupPrincipal)principal;
                        if (groupPrincipal != null && !string.IsNullOrEmpty(groupPrincipal.Name))
                        {
                            roles.Add(groupPrincipal.Name.Trim());
                        }
                    }
                }
            }
        }
    }
    catch (Exception e)
    {
        //Error handling...
    }
    return roles.ToArray();
}

答案 2 :(得分:4)

当我们的基础架构团队在线提供2012年域控制器时,我们遇到了这个问题。我们还有2012年之前的DC,因此我们间歇性地遇到了这个问题。我们提出了一个我想分享的修复方案 - 它有2个部分。

首先,安装Gary Hill提到的hotfix。这将解决以下问题:

  

枚举组时发生错误(1301)。该组的SID无法解决。

安装此修补程序后,我们认为我们是免费的。但是,安装后我们遇到了不同的间歇性错误。我们正在询问的某些群组具有空sAMAccountName属性。在Active Directory中填充了实际属性 ,但API错误地返回了空值。我认为这是Active Directory API中的某个错误,但我不知道更多。

幸运的是,我们可以通过切换到使用组Name属性而不是sAMAccountName属性来解决此问题。这对我们有用。我相信,sAMAccountName实际上已被弃用,仅出于向后兼容性原因而存在。在这种情况下,这似乎是一个合理的改变。

我附上我们的GetRolesForUser代码的缩减版本,以证明所做的更改。

using (var context = new PrincipalContext(ContextType.Domain, _domainName))
{
    try
    {
        var p = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);
        if (p == null) throw new NullReferenceException(string.Format("UserPrincipal.FindByIdentity returned null for user: {0}, this can indicate a problem with one or more of the AD controllers", username));

        var groups = p.GetAuthorizationGroups();
        var domain = username.Substring(0, username.IndexOf(@"\", StringComparison.InvariantCultureIgnoreCase)).ToLower();

        foreach (GroupPrincipal group in groups)
        {
            if (!string.IsNullOrEmpty(group.Name))
            {
                var domainGroup = domain + @"\" + group.Name.ToLower();

                if (_groupsToUse.Any(x => x.Equals(domainGroup, StringComparison.InvariantCultureIgnoreCase)))
                {
                    // Go through each application role defined and check if the AD domain group is part of it
                    foreach (string role in roleKeys)
                    {
                        string[] roleMembers = new [] { "role1", "role2" };

                        foreach (string member in roleMembers)
                        {
                            // Check if the domain group is part of the role
                            if (member.ToLower().Contains(domainGroup))
                            {
                                // Cache the Application Role (NOT the AD role)
                                results.Add(role);
                            }
                        }
                    }
                }
            }

            group.Dispose();
        }
    }
    catch (Exception ex)
    {
        throw new ProviderException("Unable to query Active Directory.", ex);
    }
}

希望有所帮助。

答案 3 :(得分:2)

我在使用包含2个工作站和50个用户/组(其中许多是内置的)的全新虚拟开发域时遇到了错误代码1301 UserPrincipal.GetAuthorizationGroups。我们运行的是Windows Server 2012 R2 Essentials,其中有两个Windows 8.1 Enterprise工作站加入了域。

我能够使用以下代码以递归方式获取用户组成员资格的列表:

class ADGroupSearch
{
    List<String> groupNames;

    public ADGroupSearch()
    {
        this.groupNames = new List<String>();
    }

    public List<String> GetGroups()
    {
        return this.groupNames;
    }

    public void AddGroupName(String groupName)
    {
        this.groupNames.Add(groupName);
    }

    public List<String> GetListOfGroupsRecursively(String samAcctName)
    {
        PrincipalContext ctx = new PrincipalContext(ContextType.Domain, System.Environment.UserDomainName);
        Principal principal = Principal.FindByIdentity(ctx, IdentityType.SamAccountName, samAcctName);
        if (principal == null)
        {
            return GetGroups();
        }
        else
        {
            PrincipalSearchResult<Principal> searchResults = principal.GetGroups();

            if (searchResults != null)
            {
                foreach (GroupPrincipal sr in searchResults)
                {
                    if (!this.groupNames.Contains(sr.Name))
                    {
                        AddGroupName(sr.Name);
                    }
                    Principal p = Principal.FindByIdentity(ctx, IdentityType.SamAccountName, sr.SamAccountName);

                    try
                    {
                        GetMembersForGroup(p);
                    }
                    catch (Exception ex)
                    {
                        //ignore errors and continue
                    }
                }

            }
            return GetGroups();
        }

    }



    private void GetMembersForGroup(Principal group)
    {
        if (group != null && typeof(GroupPrincipal) == group.GetType())
        {
            GetListOfGroupsRecursively(group.SamAccountName);
        } 
    }

    private bool IsGroup(Principal principal)
    {
        return principal.StructuralObjectClass.ToLower().Equals("group");
    }
}

答案 4 :(得分:1)

我所处的环境中有多个域林和信任。我几乎完全相同的代码运行在网站表单上,用于跨不同域执行用户安全组查找。

我在一个非常大的域中得到了这个确切的错误,其中组成员身份可以包含50多个不同的组。它在其他域林中工作正常。

在我的研究中,我发现一个看起来不相关的线程,但实际上具有相同的堆栈跟踪。它适用于在SBS上运行的远程应用程序。该线程提到错误是由组中无法解析的SIDS引起的。我相信这些将是活动目录中所谓的“墓碑式”SIDS。 See the thread here

线程建议找到墓碑化的肠子并从群体中删除它们可以解决问题。您收到的错误是否可能是因为SIDS通过单独的无关流程每12小时进行一次逻辑删除?最后,我认为这是框架中的一个错误,并且该方法不会因为逻辑删除/无法解析的SIDS而崩溃。

祝你好运!

答案 5 :(得分:1)

如果有人感兴趣,这是相同代码的VB.NET版本。 在此代码可以运行之前,您无需执行任何操作

1)您必须引用程序集System.DirectoryServices
2)确保通过&#34;用户名&#34;没有域的变量,所以如果你的域是&#34; GIS&#34;你的用户名是&#34;侯赛因&#34; Windows通常会将您验证为GIS \ Hussein。所以你必须纯粹输入用户名&#34; Hussein&#34;。我解决了区分大小写的问题。
3)方法GetGroupsNew获取用户名并返回组列表
4)方法是Memberofnew获取用户名和组并验证该用户是否属于该组,这是我感兴趣的用户。

Private Function getGroupsNew(theusername As String) As List(Of String)
    Dim lstGroups As New List(Of String)
    Try

        Dim allDomains = Forest.GetCurrentForest().Domains.Cast(Of Domain)()

        Dim allSearcher = allDomains.[Select](Function(domain)
                                                  Dim searcher As New DirectorySearcher(New DirectoryEntry("LDAP://" + domain.Name))

                                                  searcher.Filter = [String].Format("(&(&(objectCategory=person)(objectClass=user)(userPrincipalName=*{0}*)))", theusername)

                                                  Return searcher

                                              End Function)

        Dim directoryEntriesFound = allSearcher.SelectMany(Function(searcher) searcher.FindAll().Cast(Of SearchResult)().[Select](Function(result) result.GetDirectoryEntry()))

        Dim memberOf = directoryEntriesFound.[Select](Function(entry)
                                                          Using entry
                                                              Return New With { _
                                                               Key .Name = entry.Name, _
                                                               Key .GroupName = DirectCast(entry.Properties("MemberOf").Value, Object()).[Select](Function(obj) obj.ToString()) _
                                                              }
                                                          End Using

                                                      End Function)



        For Each user As Object In memberOf
            For Each groupName As Object In user.GroupName
                lstGroups.Add(groupName)
            Next
        Next

        Return lstGroups

    Catch ex As Exception
        Throw
    End Try
End Function

Private Function isMemberofGroupNew(theusername As String, thegroupname As String) As Boolean

    Try

        Dim lstGroups As List(Of String) = getGroupsNew(theusername)

        For Each sGroup In lstGroups
            If sGroup.ToLower.Contains(thegroupname.ToLower) Then Return True
        Next

        Return False


    Catch ex As Exception
        Throw
    End Try

End Function

答案 6 :(得分:0)

我们在将域控制器升级到2012后遇到了类似的问题。突然,我对user.GetAuthorizationGroups()的调用开始失败;我得到了同样的例外(错误1301)。所以,我把它改成了user.GetGroups()。这工作了一段时间,然后开始间歇性地失败“用户名或密码不好”。至少目前,我的最新解决方案似乎已经解决了这个问题。在构造用户对象之后,我还构建了一个组对象,而不是调用其中任何一个,我想要查看用户是否是其成员的每个组。即,“user.IsMemberOf(group)”。这似乎有效。

try
{
using (HostingEnvironment.Impersonate())
{
    using (var principalContext = new PrincipalContext(ContextType.Domain, "MYDOMAIN"))
    {
        using (var user = UserPrincipal.FindByIdentity(principalContext, userName))
        {
            if (user == null)
            {
                Log.Debug("UserPrincipal.FindByIdentity failed for userName = " + userName + ", thus not authorized!");
                isAuthorized = false;
            }

            if (isAuthorized)
            {
                firstName = user.GivenName;
                lastName = user.Surname;

                // so this code started failing:

                // var groups = user.GetGroups();
                // adGroups.AddRange(from @group in groups where 
                // @group.Name.ToUpper().Contains("MYSEARCHSTRING") select @group.Name);

                // so the following workaround, which calls, instead, 
                // "user.IsMemberOf(group)", 
                // appears to work (for now at least).  Will monitor for issues.

                // test membership in SuperUsers
                const string superUsersGroupName = "MyApp-SuperUsers";
                using (var superUsers = GroupPrincipal.FindByIdentity(principalContext, superUsersGroupName))
                {
                    if (superUsers != null && user.IsMemberOf(superUsers))
                        // add to the list of groups this user is a member of
                        // then do something with it later
                        adGroups.Add(superUsersGroupName);                                        
                }

答案 7 :(得分:0)

我有同样的例外。如果有人不想使用&#34; LDAP&#34;,请使用此代码。因为我有嵌套组,我使用了GetMembers(true),并且它的时间比GetMembers()稍长。

https://stackoverflow.com/a/27548271/1857271

或从此处下载修补程序:http://support.microsoft.com/kb/2830145

答案 8 :(得分:0)

面对同样的问题,枚举授权组和答案中提到的补丁不适用于我们的Web服务器。

然而,手动枚举并忽略导致故障的群组运作良好:

private static bool UserIsMember(string usr, string grp)
{
    usr = usr.ToLower();
    grp = grp.ToLower();

    using (var pc = new PrincipalContext(ContextType.Domain, "DOMAIN_NAME"))
    {
        using (var user = UserPrincipal.FindByIdentity(pc, usr))
        {
            var isMember = false;
            var authGroups = user?.GetAuthorizationGroups().GetEnumerator();

            while (authGroups?.MoveNext() ?? false)
            {
                try
                {

                    isMember = authGroups.Current.Name.ToLower().Contains(grp);
                    if (isMember) break;
                }
                catch
                {
                    // ignored
                }
            }

            authGroups?.Dispose();
            return isMember;
        }
    }
}

答案 9 :(得分:0)

我遇到的问题是,如果我通过VPN连接并使用groups=UserPrincipal.GetGroups(),则在对组进行迭代时会发生异常。

如果某人想要阅读用户的所有组,则存在以下可能性(比使用GetGroups()更快)

private IList<string> GetUserGroupsLDAP(string samAccountName)
{
    var groupList = new List<string>();
    var domainConnection = new DirectoryEntry("LDAP://" + serverName, serverUser, serverUserPassword); // probably you don't need username and password

    var samSearcher = new DirectorySearcher();
    samSearcher.SearchRoot = domainConnection;
    samSearcher.Filter = "(samAccountName=" + samAccountName + ")";

    var samResult = samSearcher.FindOne();
    if (samResult != null)
    {
        var theUser = samResult.GetDirectoryEntry();
        theUser.RefreshCache(new string[] { "tokenGroups" });

        var sidSearcher = new DirectorySearcher();
        sidSearcher.SearchRoot = domainConnection;
        sidSearcher.PropertiesToLoad.Add("name");
        sidSearcher.Filter = CreateFilter(theUser);

        foreach (SearchResult result in sidSearcher.FindAll())
        {
            groupList.Add((string)result.Properties["name"][0]);
        }
    }
    return groupList;
}

private string CreateFilter(DirectoryEntry theUser)
{
    string filter = "(|";
    foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
    {
        var SID = new SecurityIdentifier(resultBytes, 0);
        filter += "(objectSid=" + SID.Value + ")";
    }
    filter += ")";
    return filter;
}