Пароль для скидання ідентичності ASP.NET


95

Як я можу отримати пароль користувача в новій системі ідентифікації ASP.NET? Або як я можу скинути налаштування, не знаючи поточного (користувач забув пароль)?

Відповіді:


102

У поточному випуску

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

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);

ви знаєте, коли вийде версія 1.1?
Graycrow

Він все ще в альфа-версії, і 1.0 щойно вийшов. Тож припустимо багато місяців. myget.org/gallery/aspnetwebstacknightly
jd4u

11
Як не дивно, виклик методу store.SetPasswordHashAsync (cUser, hashedNewPassword) у мене не спрацював, натомість мені довелося вручну встановити cUser.PasswordHash = hashedNewPassword, а потім викликати UserManager.UpdateAsync (користувач);
Andy Mehalick

1
Непрацюючий код можливий лише в тому випадку, якщо контекст пошуку користувача та контекст зберігання відрізняються. Код був лише зразком кроків, не точним. Незабаром оновить відповідь, щоб уникнути цієї проблеми для інших.
jd4u

1
Рамка 1 не передбачає. Але Framework 2-alpha має мало функцій, які можуть забезпечити простий процес обробки запитів на скидання пароля. aspnetidentity.codeplex.com
jd4u

138

Або як я можу скинути налаштування, не знаючи поточного (користувач забув пароль)?

Якщо ви хочете змінити пароль за допомогою UserManager, але не хочете вводити поточний пароль користувача, ви можете створити маркер скидання пароля, а потім замість нього використовувати його негайно.

string resetToken = await UserManager.GeneratePasswordResetTokenAsync(model.Id);
IdentityResult passwordChangeResult = await UserManager.ResetPasswordAsync(model.Id, resetToken, model.NewPassword);

8
Це, безумовно, найкращий і найчистіший спосіб встановити новий пароль. Проблема прийнятої відповіді полягає в тому, що вона обходить перевірку складності пароля, безпосередньо звертаючись до хешера паролів.
Кріс,

6
Fyi, Ви можете отримати помилку "Жоден IUserTokenProvider не зареєстрований." якщо ви отримуєте використання вище логіки. Дивіться це stackoverflow.com/questions/22629936/… .
Прасад Канапарті

1
Це працює для Microsoft.AspNet.Identity лише у версії 2, я гадаю. Ви не можете знайти метод GeneratePasswordResetTokenAsync у версії 1.
romanoza

Спасибі за вашу відповідь. Це працює як принада для мене.
Томас Бенц,

4
Якщо ви отримали недійсний маркер , переконайтеся, що SecurityStampдля вашого користувача не вказано нульове значення. Це може трапитися з користувачами, перенесеними з інших баз даних, або користувачами, які не були створені за допомогою 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 для виклику, коли це потрібно, замість виконуваного файлу.
Mark Richman

@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


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);
}

2

Найкращий спосіб скинути пароль у Asp.Net Core Identity використання веб-API.

Примітка * : Помилка () та Результат () створені для внутрішнього використання. Ви можете повернути, що хочете.

        [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 core.

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);

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.