13

我们正在编写一个系统,允许用户通过我们内部网上的 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);
        }
    }

关于为什么会发生这种情况的任何线索?谷歌搜索并没有发现任何真正有用的东西,MSDN 上的文档也没有。

4

1 回答 1

11

我注意到的第一件事是您正在使用继承自AuthenticablePrincipal的UserPrincipal.FindByIdentity ,后者继承自Principal。我这么说是因为该类在. 如果您查看此MSDN 条目,您会在底部注意到来自 Microsoft 的Gary Caldwell说以下内容:PrincipalFindByIdentity

此调用存在非托管内存泄漏,因为底层实现使用 DirectorySearcher 和 SearchResultsCollection,但未如文档所述调用 SearchResultsCollection 上的 dispose。

我猜这是你的问题。内存泄漏导致Application Pool被填满,最终导致错误,直到Application Pool被重置,内存被释放。

当我们使用任何活动目录功能时,我们使用以下方法来完成用户密码的设置:

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

此外,如果您希望用户直接更改密码并且您不想依赖他们的诚实,您可能需要考虑使用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

这迫使用户在更改为新密码之前知道先前的密码。

我希望这有帮助,

谢谢!

于 2009-12-10T23:28:52.573 回答