Подтвердить что ты не робот

Не удалось изменить пароль для члена ASP.NET.

У меня есть этот код для изменения пароля пользователя, когда он нажимает кнопку пароля reset (с дополнительным кодом для входа в ELMAH, поэтому я могу попытаться выяснить, что происходит не так).

Это в ASP.NET MVC 2, используя стандартный поставщик членства aspnet, с простым представлением:

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

Маршрут этого представления /Account/Reset/guid, где guid - это идентификатор пользователя в базе данных членства aspnet.

Ключевой частью кода является вызов 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);
}

Изменить: добавьте щедрость, чтобы попытаться решить эту проблему. Это одна из самых неприятных проблем в моем списке проблем, и я не знаю, как это сделать.

4b9b3361

Ответ 1

Если пользователю нужен reset его пароль, есть вероятность, что их учетная запись была заблокирована из-за слишком многих недопустимых попыток. Если это так, то пароль будет reset успешно, но пользователь не может войти в систему до тех пор, пока условие блокировки не будет очищено.

Попробуйте проверить 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.

Попробуйте войти в систему и снова запустите запрос. Если signin терпит неудачу, значение FailedPasswordAttemptCount увеличилось?

Вы также можете посмотреть PasswordFormat в таблице aspnet_Membership и убедиться, что это правильное значение в зависимости от используемого вами формата (0 для Clear, 1 для Hashed и 2 для Encrypted).

Ответ 2

Хмм, я всегда использовал

bool MembershipUser.ChangePassword(string oldPassword, string newPassword)

У меня никогда не было проблемы с возвратом true, и пароль не был правильно изменен. Как я могу сказать, ваш код выглядит нормально. Трудно следовать, со всем шумом Эльмы. (вы можете удалить его или заменить простым вызовом журнала, чтобы было легче следовать).

Убедитесь, что идентификатор строки, который вы передаете в качестве аргумента, соответствует UserId для предполагаемого пользователя. Возможно, вы отправляете userId от другого пользователя и вместо этого меняете этот пароль.

Ответ 3

Отредактировано - следующий ответ ложный, см. комментарии

Так что ждите, пытаетесь ли вы найти кого-то Гидом? Выполняя

Guid userId = new Guid(id);

Фактически вы создаете гарантированный уникальный идентификатор. Поэтому я предполагаю, что вы никогда не находите пользователя, и вы никого не сбрасываете пароль. Не можете ли вы найти их по параметру id, который вы проходите?

Ответ 4

Это работает для меня:

<%@ 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>

Ответ 5

Интересно, проблема в том, что вы сбрасываете пароль прямо перед его изменением. Не вдаваясь во все внутренности класса Membership, не могли бы вы попытаться установить какую-то задержку между этими двумя командами?

Ответ 6

Какой MemberShipProvider вы используете? Это то же самое для каждого пользователя? Например, если вы используете SqlMembershipProvider и установите для параметра enablePasswordReset значение false, он будет спокойно обновлять пароль. ChangePassword в этом случае возвращает true, как будто все прошло нормально.

Ответ 7

Если вы используете встроенных провайдеров на базе SQLServer, посмотрите на свои SQL-хранимые процедуры. Это то, что выглядит по умолчанию:

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

Как вы можете видеть, оператор обновления может полностью выйти из строя, и сохраненный proc может вернуть true. Я думаю, что именно здесь появляются ваши ошибки. Могут быть проблемы с блокировкой...

Ответ 8

Ну, это, конечно, интересно. "Это работает для некоторых, а не для других" часть действительно странная.

Является ли это проблемой с прерывистой ситуацией или для нее существует всегда, а для других пользователей не всегда?

Один из других людей предложил запустить 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);

Примечание: Это не используется для производства, просто для тестирования, чтобы решить, устраняет ли это проблему.

Ответ 9

Используете ли вы один веб-сервер или несколько веб-серверов? С несколькими серверами может случиться, что машинный ключ, используемый для шифрования пароля, не является одинаковым на серверах.

Ответ 10

Может ли ваш основной блок catch выбросить само исключение, которое вы не заметили?

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.