ASP.NET членство изменить пароль не работает - PullRequest
12 голосов
/ 19 февраля 2011

У меня есть этот код для изменения пароля пользователя, когда он нажимает кнопку сброса пароля (с дополнительным кодом для входа в систему 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);
}

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

Ответы [ 10 ]

15 голосов
/ 19 февраля 2011

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

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

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

2 голосов
/ 19 февраля 2011

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

bool MembershipUser.ChangePassword(string oldPassword, string newPassword)

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

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

1 голос
/ 24 февраля 2011

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

1 голос
/ 24 февраля 2011

Ну, это, безусловно, интересный.Часть «это работает для некоторых, а не для других» действительно странная.

Это периодическая проблема, или всегда возникает для определенных пользователей, а всегда не происходит для других пользователей?

Один из других людей, предложенных здесь, предложил запустить ValidateUser(username, newPassword), чтобы подтвердить, что пользователь мог правильно пройти аутентификацию, прежде чем добиваться успеха.

Вы пробовали это?Вы можете непрерывно зацикливаться, сбрасывая + изменяя пароль до тех пор, пока ValidateUser не будет успешным, возможно, выйдя из него после сбоев N.посмотрите, решит ли это проблему.

1 голос
/ 23 февраля 2011

Если вы используете встроенные поставщики, основанные на 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

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

1 голос
/ 22 февраля 2011

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

1 голос
/ 22 февраля 2011

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

1 голос
/ 20 февраля 2011

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

<%@ 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>
1 голос
/ 19 февраля 2011

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

Так подожди, ты пытаешься найти кого-нибудь по Гиду? Делая

Guid userId = new Guid(id);

Вы практически создаете гарантированный уникальный идентификатор. Так что я думаю, что вы никогда не находите пользователя и успешно сбрасываете пароль никому. Разве вы не можете просто найти их по параметру id, который вы передаете?

0 голосов
/ 22 февраля 2011

Может ли ваш основной блок 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.

...