Пароль сброса удостоверения ASP.NET

98

Как я могу получить пароль пользователя в новой системе идентификации ASP.NET? Или как мне сбросить, не зная текущего (пользователь забыл пароль)?

Дэниел
источник

Ответы:

104

В текущем выпуске

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

ApplicationDbContext =new ApplicationDbContext()
String userId = "<YourLogicAssignsRequestedUserId>";
String newPassword = "<PasswordAsTypedByUser>";
ApplicationUser cUser = UserManager.FindById(userId);
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>();            
store.SetPasswordHashAsync(cUser, hashedNewPassword);

В AspNet Nightly Build

Платформа обновлена ​​для работы с Token для обработки таких запросов, как ForgetPassword. После выпуска ожидается простое руководство по коду.

Обновить:

Это обновление предназначено только для того, чтобы предоставить более четкие инструкции.

ApplicationDbContext context = new ApplicationDbContext();
UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(context);
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(store);
String userId = User.Identity.GetUserId();//"<YourLogicAssignsRequestedUserId>";
String newPassword = "test@123"; //"<PasswordAsTypedByUser>";
String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);                    
ApplicationUser cUser = await store.FindByIdAsync(userId);
await store.SetPasswordHashAsync(cUser, hashedNewPassword);
await store.UpdateAsync(cUser);
jd4u
источник
вы знаете, когда выйдет версия 1.1?
graycrow
Он все еще в альфа-версии, а версия 1.0 только что выпущена. Так что предположим, что много месяцев. myget.org/gallery/aspnetwebstacknightly
jd4u
11
Как ни странно, вызов метода store.SetPasswordHashAsync (cUser, hashedNewPassword) у меня не работал, вместо этого мне пришлось вручную установить cUser.PasswordHash = hashedNewPassword, а затем вызвать UserManager.UpdateAsync (user);
Энди Мехалик
1
Код не работает, только если контекст извлечения пользователя и контекст хранилища различны. Код был всего лишь примерным, а не точным. Скоро обновлю ответ, чтобы избежать этой проблемы для других.
jd4u
1
Framework 1 не предусматривает. Но у Framework 2-alpha есть несколько функций, которые могут обеспечить простой процесс обработки запросов на сброс пароля. aspnetidentity.codeplex.com
jd4u
141

Или как сделать сброс, не зная текущего (пользователь забыл пароль)?

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

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);
Дэниел Райт
источник
8
Это, безусловно, лучший и самый чистый способ установить новый пароль. Проблема с принятым ответом заключается в том, что он обходит проверку сложности пароля, напрямую обращаясь к хешеру пароля.
Крис
6
К вашему сведению, вы можете получить сообщение об ошибке «IUserTokenProvider не зарегистрирован». если вы воспользуетесь приведенной выше логикой. См. Этот stackoverflow.com/questions/22629936/… .
Прасад Канапарти,
1
Полагаю, это работает только для Microsoft.AspNet.Identity версии 2. Вы не можете найти метод GeneratePasswordResetTokenAsync в версии 1.
romanoza
Спасибо за ваш ответ. На меня это действует как оберег.
Thomas.Benz
4
Если вы получили недействительный токен , убедитесь, что SecurityStampдля вашего пользователя не указано значение null. Это может произойти с пользователями, перенесенными из других баз данных, или с пользователями, которые не были созданы с помощью UserManager.CreateAsync()метода.
Alisson
70

Не рекомендуется

Это был оригинальный ответ. Это работает, но есть проблема. Что если AddPasswordне получится? Пользователь остается без пароля.

Исходный ответ: мы можем использовать три строчки кода:

UserManager<IdentityUser> userManager = 
    new UserManager<IdentityUser>(new UserStore<IdentityUser>());

userManager.RemovePassword(userId);

userManager.AddPassword(userId, newPassword);

См. Также: http://msdn.microsoft.com/en-us/library/dn457095(v=vs.111).aspx

Сейчас рекомендуется

Вероятно, лучше использовать ответ, который предложил Эдвард Бри, а затем Дэниел Райт разработал с образцом кода.

Шон Латтин
источник
1
Слава богу, я думал, что мне придется создать новый пользовательский магазин, пока не увидел это!
Люк
Есть ли способ сделать это прямо в SQL? Я бы с удовольствием дал своему администратору баз данных sproc для вызова при необходимости вместо исполняемого файла.
Марк Ричман
@MarkRichman Это новый вопрос. Однако вы можете проверить сгенерированный T-SQL, который работает на SQL Server.
Шон Латтин,
3
При этом будьте осторожны, всякий раз, когда AddPassword выходит из строя (т.е. при недостаточной сложности пароля), пользователь останется без пароля.
Крис
1
Что ж, самый чистый подход без обхода каких-либо бизнес-правил (поскольку при прямом доступе к хеширующему паролю отсутствует проверка сложности пароля) - это то, что предложил Дэниел Райт.
Крис
29

На ваш UserManager, первый звонок GeneratePasswordResetTokenAsync . После того, как пользователь подтвердил свою личность (например, получив токен по электронной почте), передайте токен в ResetPasswordAsync .

Эдвард Брей
источник
2
Попытка выяснить, почему ResetPasswordAsync требует идентификатора пользователя, и разумный способ получить его от пользователя, когда он появляется с токеном. GeneratePasswordReset использует токен с более чем 150 символами ... кажется, что этого было бы достаточно для криптографического хранения идентификатора пользователя, поэтому мне не нужно реализовывать это самостоятельно. :(
pettys
Я предполагаю, что он запрашивает идентификатор пользователя, чтобы он мог ввести токен сброса в базу данных идентификации для этого идентификатора пользователя. Если бы этого не произошло, как бы фреймворк узнал, действителен ли токен? Вы должны иметь возможность получить идентификатор пользователя с помощью User.Identity.GetUserId () или аналогичного.
Райан Баддом
1
Требование идентификатора пользователя - глупый выбор со стороны API, токен уже находится в базе данных, когда вызывается ResetPassword (async), и этого должно быть достаточно, чтобы проверить его на вход.
Филип
@Filip, преимущество ResetPasswordAsyncиспользования идентификатора пользователя заключается в том, что провайдеру удостоверений необходимо индексировать только идентификаторы пользователей, но не токены. Это позволяет лучше масштабироваться при большом количестве пользователей.
Эдвард Брей
2
@ Эдвард Брей, а как получить идентификатор пользователя для вызова сброса?
Филип
2
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(model.Token, model.Password);
if (result.Success)
{
    message = "The password has been reset.";
    return RedirectToAction("PasswordResetCompleted", new { message = message });
}
else
{
    AddErrors(result);
}

Этот фрагмент кода взят из проекта AspNetIdentitySample, доступного на github.

Sclarson
источник
2

Создать метод в UserManager<TUser, TKey>

public Task<IdentityResult> ChangePassword(int userId, string newPassword)
{
     var user = Users.FirstOrDefault(u => u.Id == userId);
     if (user == null)
          return new Task<IdentityResult>(() => IdentityResult.Failed());

     var store = Store as IUserPasswordStore<User, int>;
     return base.UpdatePassword(store, user, newPassword);
}
tmg
источник
2

Лучший способ сбросить пароль в Asp.Net Core Identity использовать для веб-API.

Примечание * : Error () и Result () созданы для внутреннего использования. Вы можете вернуться, если хотите.

        [HttpPost]
        [Route("reset-password")]
        public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            try
            {
                if (model is null)
                    return Error("No data found!");


                var user = await _userManager.FindByIdAsync(AppCommon.ToString(GetUserId()));
                if (user == null)
                    return Error("No user found!");

                Microsoft.AspNetCore.Identity.SignInResult checkOldPassword =
                    await _signInManager.PasswordSignInAsync(user.UserName, model.OldPassword, false, false);

                if (!checkOldPassword.Succeeded)
                    return Error("Old password does not matched.");

                string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
                if (string.IsNullOrEmpty(resetToken))
                    return Error("Error while generating reset token.");

                var result = await _userManager.ResetPasswordAsync(user, resetToken, model.Password);

                if (result.Succeeded)
                    return Result();
                else
                    return Error();
            }
            catch (Exception ex)
            {
                return Error(ex);
            }
        }
Маниш Вадхер
источник
2
Это сработало и с Fx v 4.5. Другое решение не сработало. По сути, это тоже было намного проще. Вам даже не нужно получать пользователя, поскольку все методы будут принимать идентификатор. Мне просто это было нужно для временного одноразового сброса в моем интерфейсе администратора, поэтому мне не нужны все проверки ошибок.
Стив Хайнер,
1

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

Рахул Гарг
источник
1

Я думаю, что руководство Microsoft по ASP.NET Identity - хорошее начало.

https://docs.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity

Заметка:

Если вы не используете AccountController и не хотите сбрасывать пароль, используйте Request.GetOwinContext().GetUserManager<ApplicationUserManager>();. Если у вас нет того же OwinContext, вам нужно создать новый, DataProtectorTokenProviderподобный тому, который OwinContextиспользуется. По умолчанию смотрите App_Start -> IdentityConfig.cs. Должно выглядеть примерно так new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));.

Можно было создать так:

Без Овин:

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset()
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testReset")]
public IHttpActionResult TestReset(string token)
{
    var db = new ApplicationDbContext();
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
    var provider = new DpapiDataProtectionProvider("SampleAppName");
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(
        provider.Create("SampleTokenName"));
    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

С Овином:

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin()
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";

    var user = new ApplicationUser() { UserName = email, Email = email };

    var identityUser = manager.FindByEmail(email);

    if (identityUser == null)
    {
        manager.Create(user);
        identityUser = manager.FindByEmail(email);
    }

    var token = manager.GeneratePasswordResetToken(identityUser.Id);
    return Ok(HttpUtility.UrlEncode(token));
}

[HttpGet]
[AllowAnonymous]
[Route("testResetWithOwin")]
public IHttpActionResult TestResetWithOwin(string token)
{
    var manager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();

    var email = "test@test.com";
    var identityUser = manager.FindByEmail(email);
    var valid = Task.Run(() => manager.UserTokenProvider.ValidateAsync("ResetPassword", token, manager, identityUser)).Result;
    var result = manager.ResetPassword(identityUser.Id, token, "TestingTest1!");
    return Ok(result);
}

DpapiDataProtectionProviderИ DataProtectorTokenProviderдолжны быть созданы с таким же именем для сброса пароля к работе. Использование Owin для создания токена сброса пароля и последующее создание нового DpapiDataProtectionProviderс другим именем не сработает.

Код, который я использую для идентификации ASP.NET:

Web.Config:

<add key="AllowedHosts" value="example.com,example2.com" />

AccountController.cs:

[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await UserManager.FindByEmailAsync(email);
    if (user == null)
    {
        Logger.Warn("Password reset token requested for non existing email");
        // Don't reveal that the user does not exist
        return NoContent();
    }

    //Prevent Host Header Attack -> Password Reset Poisoning. 
    //If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
    //See https://security.stackexchange.com/a/170759/67046
    if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
            Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
            return BadRequest();
    }

    Logger.Info("Creating password reset token for user id {0}", user.Id);

    var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
    var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
    var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";

    var subject = "Client - Password reset.";
    var body = "<html><body>" +
               "<h2>Password reset</h2>" +
               $"<p>Hi {user.FullName}, <a href=\"{callbackUrl}\"> please click this link to reset your password </a></p>" +
               "</body></html>";

    var message = new IdentityMessage
    {
        Body = body,
        Destination = user.Email,
        Subject = subject
    };

    await UserManager.EmailService.SendAsync(message);

    return NoContent();
}

[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
    if (!ModelState.IsValid)
        return NoContent();

    var user = await UserManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        Logger.Warn("Reset password request for non existing email");
        return NoContent();
    }            

    if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
    {
        Logger.Warn("Reset password requested with wrong token");
        return NoContent();
    }

    var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);

    if (result.Succeeded)
    {
        Logger.Info("Creating password reset token for user id {0}", user.Id);

        const string subject = "Client - Password reset success.";
        var body = "<html><body>" +
                   "<h1>Your password for Client was reset</h1>" +
                   $"<p>Hi {user.FullName}!</p>" +
                   "<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
                   "</body></html>";

        var message = new IdentityMessage
        {
            Body = body,
            Destination = user.Email,
            Subject = subject
        };

        await UserManager.EmailService.SendAsync(message);
    }

    return NoContent();
}

public class ResetPasswordRequestModel
{
    [Required]
    [Display(Name = "Token")]
    public string Token { get; set; }

    [Required]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}
Огглас
источник
1

Я провел небольшое расследование, и решение, которое работает для меня, было смесью нескольких решений, основанных в этом посте.

Я в основном компилирую это решение и публикую то, что мне подходит. В моем случае я не хочу использовать токены из ядра .net.

public async Task ResetPassword(string userId, string password)
{
    var user = await _userManager.FindByIdAsync(userId);
    var hashPassword= _userManager.PasswordHasher.HashPassword(user, password);
    user.PasswordHash = passwordHash;
    await _userManager.UpdateAsync(user);

}
После
источник
«то, что работает для меня», просто недостаточно для чего-то, что имеет отношение к безопасности. Я хочу использовать как можно больше готовых .NET Core.
Heinzlmaen