ASP.NET成员身份更改密码不起作用

时间:2011-02-19 02:32:06

标签: c# .net asp.net-mvc asp.net-membership

我有这个代码用于在用户点击密码重置按钮时更改用户的密码(使用额外的代码登录ELMAH,这样我就可以尝试找出问题所在。)

这是在ASP.NET MVC 2中,使用标准的aspnet成员资格提供程序,使用这样的简单视图:

New Password:     ______
Confirm Password: ______
[Reset] [Cancel]

此视图的路径为/Account/Reset/guid,其中guid是aspnet成员资格数据库中的用户ID。

代码的关键部分是它调用user.ChangePassword()的位置。您可以看到它在成功时记录消息。问题是对于某些用户,会记录成功消息,但他们无法使用新密码登录。对于其他用户,它会记录成功消息,并且他们可以登录。

if (user.ChangePassword(pwd, confirmPassword))
{
    ErrorSignal.FromCurrentContext().Raise(
        new Exception("ResetPassword - changed successfully!"));
    return Json(new { 
        Msg = "You have reset your password successfully." }, 
        JsonRequestBehavior.AllowGet);
 }

完整的代码清单是:

[HttpPost]
public JsonResult ResetPassword(string id, string newPassword, string confirmPassword)
{
    ErrorSignal.FromCurrentContext().Raise(new Exception("ResetPassword started for " + id));

    ViewData["PasswordLength"] = Membership.MinRequiredPasswordLength;

    if (string.IsNullOrWhiteSpace(newPassword))
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - new password was blank."));
        ModelState.AddModelError("_FORM", "Please enter a new password.");
        return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
    }

    if (newPassword.Length < Membership.MinRequiredPasswordLength)
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - new password was less than minimum length."));
        ModelState.AddModelError("_FORM", 
            string.Format("The password must be at least {0} characters long.", 
            Membership.MinRequiredPasswordLength));
        return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
    }

    if (string.IsNullOrWhiteSpace(confirmPassword))
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - confirm password was blank."));
        ModelState.AddModelError("_FORM", 
            "Please enter the same new password in the confirm password textbox.");
        return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
    }

    if (confirmPassword.Length < Membership.MinRequiredPasswordLength)
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - confirm password was less than minimum length."));
        ModelState.AddModelError("_FORM", 
            string.Format("The password must be at least {0} characters long.", 
            Membership.MinRequiredPasswordLength));
        return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
    }

    if (confirmPassword != newPassword)
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - new password did not match the confirm password."));
        ModelState.AddModelError("_FORM", "Please enter the same password again.");
        return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
    }

    bool isMatch = ValidationHelper.IsGUID(id);
    if (string.IsNullOrWhiteSpace(id) || !isMatch)
    {
        ErrorSignal.FromCurrentContext().Raise(
            new Exception("ResetPassword - id was not a guid."));
        ModelState.AddModelError("_FORM", "An invalid ID value was passed in through the URL");
    }
    else
    {
        //ID exists and is kosher, see if this user is already approved
        //Get the ID sent in the querystring
        Guid userId = new Guid(id);

        try
        {
            //Get information about the user
            MembershipUser user = Membership.GetUser(userId);
            if (user == null)
            {
                //could not find the user
                ErrorSignal.FromCurrentContext().Raise(
                    new Exception("ResetPassword - could not find user by id " + id));
                ModelState.AddModelError("_FORM", 
                    "The user account can not be found in the system.");
            }
            else
            {
                ErrorSignal.FromCurrentContext().Raise(
                    new Exception("ResetPassword - user is " + user.UserName));
                string pwd = user.ResetPassword();

                if (user.ChangePassword(pwd, confirmPassword))
                {
                    ErrorSignal.FromCurrentContext().Raise(
                        new Exception("ResetPassword - changed successfully!"));
                    return Json(new { 
                        Msg = "You have reset your password successfully." }, 
                        JsonRequestBehavior.AllowGet);
                }
                ErrorSignal.FromCurrentContext().Raise(
                    new Exception("ResetPassword 
                    - failed to change the password, for an unknown reason"));
            }
        }
        catch (Exception ex)
        {
            ErrorSignal.FromCurrentContext().Raise(
                new Exception("ResetPassword: " + ex));
            return Json(new { Error = ex.Message + " -> " 
                + ex.InnerException.Message }, JsonRequestBehavior.AllowGet);
        }
    }

    return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
}

编辑:添加赏金以尝试解决此问题。这是我的问题列表中最烦人的问题之一,我不知道如何继续。

10 个答案:

答案 0 :(得分:15)

如果用户需要重置密码,他们的帐户可能会被锁定,因为无效的尝试次数过多。如果是这种情况,则密码正在成功重置,但用户无法登录,直到锁定条件被清除。

尝试检查MembershipUser.IsLockedOut

  

用户最常被锁定   无法通过验证   ValidateUser方法的时候   达到MaxInvalidPasswordAttempts   在PasswordAttemptWindow中。

     

将此属性设置为false并允许   用户尝试再次登录,即可   使用UnlockUser方法。

修改

你还检查过IsApproved吗?验证将失败,对于用户来说这是false

另外,假设默认情况下成员资格提供程序是指SqlMembershipProvider,您是否可以对数据库运行以下查询并确保所有内容都正确无误?

select IsApproved, IsLockedOut, FailedPasswordAttemptCount
from aspnet_Membership
where ApplicationId = @yourApplicationId and UserId = @userId

在尝试登录以验证IsApproved并且IsLockedOut正常之前,请尝试执行查询。另请注意FailedPasswordAttemptCount的值。

尝试登录,然后再次运行查询。如果登录失败,FailedPasswordAttemptCount的值是否已递增?

您还可以查看aspnet_Membership表中的PasswordFormat,并确保它是正确的值,具体取决于您使用的格式(0代表清除,1代表哈希,2代表加密)。

答案 1 :(得分:2)

嗯,我一直用

bool MembershipUser.ChangePassword(string oldPassword, string newPassword)

我从未遇到过返回true且密码未正确更改的问题。 我可以告诉您的代码看起来不错。很难跟上,那里有所有的Elmah噪音。 (您可能希望将其删除或替换为简单的日志调用,以便更容易理解)。

验证作为参数传递的字符串id是否与目标用户的UserId相对应。您可能正在从其他用户发送userId并改为更改该用户密码。

答案 2 :(得分:1)

已编辑 - 以下答案为false,请参阅评论

等等,你是不是想通过Guid找人?通过做

Guid userId = new Guid(id);

您实际上创建的是唯一ID。所以我的猜测是你永远找不到用户而且你正在为没人成功重置密码。您是否可以通过传入的id参数找到它们?

答案 3 :(得分:1)

这对我有用:

<%@ Page Title="Change Password" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="ChangePassword.aspx.cs" Inherits="WebPages.Account.ChangePassword" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Change Password
    </h2>
    <p>
        Use the form below to change your password.
    </p>
    <p>
        New passwords are required to be a minimum of <%= Membership.MinRequiredPasswordLength %> characters in length.
    </p>
    <asp:ChangePassword ID="ChangeUserPassword" runat="server" CancelDestinationPageUrl="~/" EnableViewState="false" RenderOuterTable="false" 
        OnChangedPassword="ChangeUserPassword_ChangedPassword">
        <ChangePasswordTemplate>
            <span class="failureNotification">
                <asp:Literal ID="FailureText" runat="server"></asp:Literal>
            </span>
            <asp:ValidationSummary ID="ChangeUserPasswordValidationSummary" runat="server" CssClass="failureNotification" 
                 ValidationGroup="ChangeUserPasswordValidationGroup"/>
            <div class="accountInfo">
                <fieldset class="changePassword">
                    <legend>Account Information</legend>
                    <p>
                        <asp:Label ID="CurrentPasswordLabel" runat="server" AssociatedControlID="CurrentPassword">Old Password:</asp:Label>
                        <asp:TextBox ID="CurrentPassword" runat="server" CssClass="passwordEntry" TextMode="Password"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="CurrentPasswordRequired" runat="server" ControlToValidate="CurrentPassword" 
                             CssClass="failureNotification" ErrorMessage="Password is required." ToolTip="Old Password is required." 
                             ValidationGroup="ChangeUserPasswordValidationGroup">*</asp:RequiredFieldValidator>
                    </p>
                    <p>
                        <asp:Label ID="NewPasswordLabel" runat="server" AssociatedControlID="NewPassword">New Password:</asp:Label>
                        <asp:TextBox ID="NewPassword" runat="server" CssClass="passwordEntry" TextMode="Password"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="NewPasswordRequired" runat="server" ControlToValidate="NewPassword" 
                             CssClass="failureNotification" ErrorMessage="New Password is required." ToolTip="New Password is required." 
                             ValidationGroup="ChangeUserPasswordValidationGroup">*</asp:RequiredFieldValidator>
                    </p>
                    <p>
                        <asp:Label ID="ConfirmNewPasswordLabel" runat="server" AssociatedControlID="ConfirmNewPassword">Confirm New Password:</asp:Label>
                        <asp:TextBox ID="ConfirmNewPassword" runat="server" CssClass="passwordEntry" TextMode="Password"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="ConfirmNewPasswordRequired" runat="server" ControlToValidate="ConfirmNewPassword" 
                             CssClass="failureNotification" Display="Dynamic" ErrorMessage="Confirm New Password is required."
                             ToolTip="Confirm New Password is required." ValidationGroup="ChangeUserPasswordValidationGroup">*</asp:RequiredFieldValidator>
                        <asp:CompareValidator ID="NewPasswordCompare" runat="server" ControlToCompare="NewPassword" ControlToValidate="ConfirmNewPassword" 
                             CssClass="failureNotification" Display="Dynamic" ErrorMessage="The Confirm New Password must match the New Password entry."
                             ValidationGroup="ChangeUserPasswordValidationGroup">*</asp:CompareValidator>
                    </p>
                </fieldset>
                <p class="submitButton">
                    <asp:Button ID="CancelPushButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"/>
                    <asp:Button ID="ChangePasswordPushButton" runat="server" CommandName="ChangePassword" Text="Change Password" 
                         ValidationGroup="ChangeUserPasswordValidationGroup"/>
                </p>
            </div>
        </ChangePasswordTemplate>
        <SuccessTemplate>
            <div class="accountInfo">
                <fieldset class="changePassword">
                    <legend>Password changed</legend>
                        <p>
                            Your password has been changed. A confirmation e-mail has been sent to you.
                        </p>
                </fieldset>
            </div>
        </SuccessTemplate>
    </asp:ChangePassword>
</asp:Content>

答案 4 :(得分:1)

我想知道问题是您在更改密码之前是否正在重置密码。如果没有进入Membership类的所有内部,你能尝试在这两个命令之间加入某种延迟吗?

答案 5 :(得分:1)

您使用的是哪个MemberShipProvider?每个用户都一样吗?例如,如果您使用SqlMembershipProvider并将enablePasswordReset设置为false,则它将悄然无法更新密码。在这种情况下,ChangePassword返回true,好像一切都很顺利。

答案 6 :(得分:1)

如果您使用内置的基于SQLServer的提供程序,请查看您的SQL存储过程。这是我的默认proc看起来的样子:

ALTER PROCEDURE dbo.aspnet_Membership_SetPassword
    @ApplicationName  nvarchar(256),
    @UserName         nvarchar(256),
    @NewPassword      nvarchar(128),
    @PasswordSalt     nvarchar(128),
    @CurrentTimeUtc   datetime,
    @PasswordFormat   int = 0
AS
BEGIN
    DECLARE @UserId uniqueidentifier
    SELECT  @UserId = NULL
    SELECT  @UserId = u.UserId
    FROM    dbo.aspnet_Users u, dbo.aspnet_Applications a, dbo.aspnet_Membership m
    WHERE   LoweredUserName = LOWER(@UserName) AND
            u.ApplicationId = a.ApplicationId  AND
            LOWER(@ApplicationName) = a.LoweredApplicationName AND
            u.UserId = m.UserId

    IF (@UserId IS NULL)
        RETURN(1)

    UPDATE dbo.aspnet_Membership
    SET Password = @NewPassword, PasswordFormat = @PasswordFormat, PasswordSalt = @PasswordSalt,
        LastPasswordChangedDate = @CurrentTimeUtc
    WHERE @UserId = UserId
    RETURN(0)
END

正如您所看到的,update语句可能完全失败,并且存储过程可能返回true。我认为这是你的错误可能来自的地方。可能是锁定问题......

答案 7 :(得分:1)

这当然是一个有趣的问题。 “它适用于某些人,而不适用于其他人”部分非常奇怪。

这是一个间歇性问题,还是某些用户始终,而其他用户始终不会

此处的其他人建议运行ValidateUser(username, newPassword)以确认用户在成功之前可以正确进行身份验证。

你试过这个吗?您可以继续循环,重置+更改密码,直到ValidateUser成功,可能在N次失败后退出。

bool success = false;
int numAttempts = 0;
do
{
    string pwd = user.ResetPassword();
    if (user.ChangePassword(pwd, confirmPassword))
    {
        success = Membership.ValidateUser(user.UserName, pwd);
    }
    numAttempts++;
} while(numAttempts < 5 && !success);

注意:这不适用于生产,只是用于测试是否可以解决问题。

答案 8 :(得分:1)

您使用的是1个网络服务器还是多个网络服务器?对于多个服务器,可能是用于加密密码的机器密钥在al服务器上是不同的。

答案 9 :(得分:0)

您的主要捕获块是否会抛出您未注意到的异常?

catch (Exception ex)
{
    ErrorSignal.FromCurrentContext().Raise(new Exception("ResetPassword: " + ex));
    return Json(new { Error = ex.Message + " -> " 
            + ex.InnerException.Message }, JsonRequestBehavior.AllowGet);
}

ex.InnerException.Message语句不安全,因为它可能抛出NullReferenceException。