System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity的奇怪问题

13 directoryservices memory-leaks ldap active-directory account-management

我们正在编写一个允许用户通过Intranet上的Web应用程序更改其帐户密码的系统.

起初,一切似乎都在顺利进行.在开发期间,我们的测试帐户的密码可以毫无问题地更改.

然而,当我们制作系统时,我们开始遇到问题.以下是症状:

  1. 一开始,一切都很好.用户可以更改其密码.
  2. 在某些时候,UserPrincipal.FindByIdentity中出现以下错误:"System.Runtime.InteropServices.COMException:身份验证机制未知."
  3. 从那时起,尝试通过Web应用程序更改密码会导致错误:"System.Runtime.InteropServices.COMException:服务器无法运行."
  4. 如果我手动回收应用程序池,一切似乎都会自行解决,直到更多错误开始发生...即,该过程在第1阶段重新开始.

这是相关的代码片段:


    private static PrincipalContext CreateManagementContext() {
        return new PrincipalContext(
            ContextType.Domain, 
            ActiveDirectoryDomain, 
            ActiveDirectoryManagementAccountName,
            ActiveDirectoryManagementAccountPassword);
    }


    private static void ChangeActiveDirectoryPasword(string username, string password) {
        if (username == null) throw new ArgumentNullException("username");
        if (password == null) throw new ArgumentNullException("password");

        using (var context = CreateManagementContext())
        using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)) {
            user.SetPassword(password);
        }
    }
Run Code Online (Sandbox Code Playgroud)

关于为什么会发生这种情况的任何线索?谷歌搜索没有提供任何真正有用的信息,MSDN上的文档也没有.

Sco*_*ott 11

我注意到的第一件事是你正在使用UserPrincipal.FindByIdentity,它继承自Principal继承自AuthenticablePrincipal.我说这一切都是因为这个类中有一个已知的内存泄漏.如果您查看此MSDN条目,您会在底部注意到Microsoft的Gary Caldwell说:PrincipalFindByIdentity

此调用具有非托管内存泄漏,因为底层实现使用DirectorySearcher和SearchResultsCollection,但不会像文档描述那样在SearchResultsCollection上调用dispose.

我猜这是你的问题.内存泄漏导致应用程序池填满并最终导致错误,直到重置应用程序池并释放内存.

当我们使用任何活动目录函数时,我们使用以下内容来完成用户密码的设置:

Public Shared Function GetUserAccount(ByVal username As String) As DirectoryEntry
    Dim rootPath As String = GetRootPath()
    Using objRootEntry As New DirectoryEntry(rootPath)
        Using objAdSearcher As New DirectorySearcher(objRootEntry)
            objAdSearcher.Filter = "(&(objectClass=user)(samAccountName=" & username & "))"
            Dim objResult As SearchResult = objAdSearcher.FindOne()
            If objResult IsNot Nothing Then Return objResult.GetDirectoryEntry()
        End Using
    End Using
    Return Nothing
End Function

Public Shared Sub SetPassword(ByVal username As String, ByVal newPassword As String)
    Using objUser As DirectoryEntry = GetUserAccount(username)
        If objUser Is Nothing Then Throw New UserNotFoundException(username)
        Try
            objUser.Invoke("SetPassword", newPassword)
            objUser.CommitChanges()
        Catch ex As Exception
            Throw New Exception("Could not change password for " & username & ".", ex)
        End Try
    End Using
End Sub
Run Code Online (Sandbox Code Playgroud)

此外,如果您希望用户直接更改密码而您不想依赖他们的诚实,您可能需要考虑使用ChangePasswordLDAP 的功能,如下所示:

Public Shared Sub ChangePassword(ByVal username As String, ByVal oldPassword As String, ByVal newPassword As String)
    Using objUser As DirectoryEntry = GetUserAccount(username)
        If objUser Is Nothing Then Throw New UserNotFoundException(username)
        Try
            objUser.Invoke("ChangePassword", oldPassword, newPassword)
            objUser.CommitChanges()
        Catch ex As TargetInvocationException
            Throw New Exception("Could not change password for " & username & ".", ex)
        End Try
    End Using
End Sub
Run Code Online (Sandbox Code Playgroud)

这会强制用户在更改为新密码之前知道先前的密码.

我希望这有帮助,

谢谢!