27

研究:

解决方法的类似问题,但不是现有问题的实际解决方案

指向 Microsoft End Point 更新的类似问题是罪魁祸首

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

背景:

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

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

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

在这一点上,我正在尝试改进针对网络中特定域控制器以及新 DC 的 cron,并使用当前无法产生更有针对性的异常时间的标准 LDAP 查询。到目前为止,我们在一台 Web 服务器上发现它失败的日子没有规律,但它会在大约 12 小时内恢复。最新结果显示,Group SID 解析在上午 8 点到晚上 8 点之间失败,然后恢复,几天后,它会在晚上 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 上的第一篇文章,我经常在这里研究,但直到现在才参与讨论。如果我应该在其他地方发布,请原谅我,并在发布之前随时指出更好的步骤。

2013 年 6 月 13 日更新:

在 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;

    }
4

10 回答 10

18

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

经过一些实验,我们发现我们的代码可以在 Server 2012 上正常运行,但在客户端系统运行 Server 2008 时遇到 1301 错误代码。有关所发生情况的关键信息可在此处找到:

MS 博客从德语翻译

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

无法映射 SID S-1-18-1 和 SID S-1-18-2

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

加里

于 2013-10-15T14:45:52.450 回答
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();
}
于 2014-08-18T16:57:56.100 回答
4

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

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

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

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

Name幸运的是,我们能够通过切换到使用 group属性而不是属性来解决这个问题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);
    }
}

希望有帮助。

于 2014-01-20T13:16:04.303 回答
2

UserPrincipal.GetAuthorizationGroups我在使用包含 2 个工作站和 50 个用户/组(其中许多是内置的)的全新虚拟开发域时遇到错误代码 1301 。我们正在运行 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");
    }
}
于 2014-05-29T19:04:48.680 回答
1

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

我在一个非常大的域中遇到了这个确切的错误,其中组成员身份可以包括 50 多个不同的组。它在其他域森林中运行良好。

在我的研究中,我发现了一个看起来不相关但实际上具有相同堆栈跟踪的线程。它适用于在 SBS 上运行的远程应用程序。该线程提到该错误是由组中无法解决的 SIDS 引起的。我相信这些将是活动目录中所谓的“墓碑”小岛屿发展中国家。请参阅此处的线程

该线程建议找到墓碑并将它们从组中删除可以解决问题。您收到的错误是否可能是因为 SIDS 每 12 小时被一个单独的不相关进程删除?最终,我相信这是框架中的一个错误,并且该方法不应该因为墓碑/无法解析的 SIDS 而崩溃。

祝你好运!

于 2013-08-23T18:13:39.983 回答
1

如果有人感兴趣,这是相同代码的 VB.NET 版本。在此代码可以工作之前您需要做的几件事

1)您必须引用程序集 System.DirectoryServices
2)确保传递“用户名”变量而不传递域,因此如果您的域是“GIS”并且您的用户名是“Hussein”,Windows 通常会将您验证为 GIS\Hussein。因此,您必须仅发送用户名“Hussein”。我制定了区分大小写的东西。
3) GetGroupsNew 方法接受用户名并返回组列表
4) isMemberofnew 方法接受用户名和组并验证该用户是否属于该组,这是我感兴趣的。

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
于 2015-07-01T06:18:35.797 回答
0

我有同样的例外。如果有人不想使用“LDAP”,请使用此代码。因为我有嵌套组,所以我使用了 GetMembers(true),它的时间比 GetMembers() 长一点。

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

或从这里下载修复:http: //support.microsoft.com/kb/2830145

于 2014-12-18T14:03:38.530 回答
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);                                        
                }
于 2014-01-28T13:13:44.730 回答
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;
        }
    }
}
于 2017-04-05T18:24:43.273 回答
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;
}
于 2018-11-06T21:38:08.443 回答