ASP.NET Identity รีเซ็ตรหัสผ่าน


100

ฉันจะรับรหัสผ่านของผู้ใช้ในระบบ ASP.NET Identity ใหม่ได้อย่างไร? หรือฉันจะรีเซ็ตโดยไม่ทราบรหัสปัจจุบันได้อย่างไร (ผู้ใช้ลืมรหัสผ่าน)?

คำตอบ:


105

ในรุ่นปัจจุบัน

สมมติว่าคุณได้จัดการการตรวจสอบคำขอรีเซ็ตรหัสผ่านที่ลืมแล้วให้ใช้รหัสต่อไปนี้เป็นขั้นตอนโค้ดตัวอย่าง

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

เฟรมเวิร์กได้รับการอัปเดตเพื่อทำงานร่วมกับโทเค็นสำหรับจัดการคำขอเช่น 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
Framework 1 ไม่มีให้ แต่ Framework 2-alpha มีคุณสมบัติบางอย่างที่สามารถให้กระบวนการง่ายๆในการจัดการคำขอรีเซ็ตรหัสผ่าน aspnetidentity.codeplex.com
jd4u

143

หรือฉันจะรีเซ็ตโดยไม่ทราบรหัสปัจจุบันได้อย่างไร (ผู้ใช้ลืมรหัสผ่าน)?

หากคุณต้องการเปลี่ยนรหัสผ่านโดยใช้ 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

ขอบคุณสำหรับคำตอบ. มันเป็นเสน่ห์สำหรับฉัน
Thomas.Benz

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

แนะนำตอนนี้

อาจจะดีกว่าถ้าใช้คำตอบที่EdwardBrey เสนอแล้วDanielWright ได้อธิบายรายละเอียดในภายหลังด้วยตัวอย่างโค้ด


1
ขอบคุณพระเจ้าสำหรับสิ่งนี้ฉันคิดว่าฉันต้องสร้างที่เก็บผู้ใช้ใหม่จนกว่าฉันจะเห็นสิ่งนี้!
ลุค

มีวิธีใดบ้างที่จะทำสิ่งนี้โดยตรงใน SQL? ฉันชอบที่จะมอบ sproc ให้ DBA ของฉันเพื่อโทรเมื่อจำเป็นแทนที่จะเป็นไฟล์ปฏิบัติการ
Mark Richman

@MarkRichman นั่นเป็นคำถามใหม่ สิ่งหนึ่งที่คุณสามารถทำได้คือการตรวจสอบ T-SQL ที่สร้างขึ้นซึ่งทำงานบน SQL Server
Shaun Luttin

3
ระวังเรื่องนี้เมื่อใดก็ตามที่ AddPassword ล้มเหลว (เช่นรหัสผ่านที่ซับซ้อนไม่เพียงพอ) ผู้ใช้จะถูกทิ้งไว้โดยไม่มีรหัสผ่าน
คริส

1
แนวทางที่สะอาดที่สุดโดยไม่ต้องข้ามกฎเกณฑ์ทางธุรกิจใด ๆ (เพราะเมื่อคุณเข้าถึงแฮชรหัสผ่านโดยตรงจะไม่มีการตรวจสอบความซับซ้อนของรหัสผ่าน) คือสิ่งที่ Daniel Wright เสนอ
คริส

29

บนUserManagerแรกเรียกGeneratePasswordResetTokenAsync เมื่อผู้ใช้มีการยืนยันตัวตนของเขา (เช่นโดยได้รับโทเค็นในอีเมล) ผ่านโทเค็นการResetPasswordAsync


2
พยายามหาคำตอบว่าเหตุใด ResetPasswordAsync จึงต้องใช้ ID ผู้ใช้และวิธีที่เหมาะสมในการรับรหัสจากผู้ใช้เมื่อพวกเขาแสดงโทเค็น GeneratePasswordReset ใช้โทเค็นที่มีมากกว่า 150 ตัวอักษร ... ดูเหมือนว่าจะเพียงพอที่จะเก็บรหัสผู้ใช้ด้วยการเข้ารหัสดังนั้นฉันจึงไม่ต้องใช้มันด้วยตัวเอง :(
อนุ

ฉันคิดว่ามันกำลังขอ ID ผู้ใช้เพื่อให้สามารถป้อนโทเค็นรีเซ็ตลงในฐานข้อมูลเอกลักษณ์เทียบกับ ID ผู้ใช้นั้น หากไม่ทำเช่นนี้เฟรมเวิร์กจะรู้ได้อย่างไรว่าโทเค็นถูกต้องหรือไม่ คุณควรจะสามารถดึง ID ผู้ใช้โดยใช้ User.Identity.GetUserId () หรือที่คล้ายกัน
Ryan Buddicom

1
การกำหนดรหัสผู้ใช้เป็นทางเลือกที่โง่เง่าในส่วนของ API โทเค็นนั้นอยู่ในฐานข้อมูลแล้วเมื่อมีการเรียก ResetPassword (async) และควรจะเพียงพอที่จะตรวจสอบความถูกต้องกับอินพุต
ฟิลิป

@Filip ข้อดีของResetPasswordAsyncการใช้ ID ผู้ใช้คือผู้ให้บริการข้อมูลประจำตัวต้องทำดัชนี ID ผู้ใช้เท่านั้นไม่ใช่โทเค็นด้วย ซึ่งจะช่วยให้ปรับขนาดได้ดีขึ้นหากมีผู้ใช้จำนวนมาก
Edward Brey

2
@Edward Brey ดีคุณจะดึงรหัสผู้ใช้สำหรับการโทรรีเซ็ตได้อย่างไร?
ฟิลิป

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 ใช้สำหรับ Web 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 เช่นกัน วิธีแก้ปัญหาอื่นไม่ได้ผล โดยพื้นฐานแล้วสิ่งนี้ก็ง่ายกว่ามากเช่นกัน คุณไม่จำเป็นต้องรับผู้ใช้เนื่องจากวิธีการทั้งหมดจะยอมรับรหัส ฉันแค่ต้องการมันสำหรับการรีเซ็ตแบบครั้งเดียวชั่วคราวในอินเทอร์เฟซผู้ดูแลระบบของฉันดังนั้นฉันจึงไม่จำเป็นต้องตรวจสอบข้อผิดพลาดทั้งหมด
Steve Hiner

1

ในกรณีของการรีเซ็ตรหัสผ่านขอแนะนำให้รีเซ็ตโดยส่งโทเค็นการรีเซ็ตรหัสผ่านไปยังอีเมลของผู้ใช้ที่ลงทะเบียนและขอให้ผู้ใช้ระบุรหัสผ่านใหม่ หากได้สร้างไลบรารี. NET ที่ใช้งานง่ายบนเฟรมเวิร์ก Identity ด้วยการกำหนดค่าเริ่มต้น สามารถดูรายละเอียดได้ที่blog linkและsource codeที่ 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 และ wan't Request.GetOwinContext().GetUserManager<ApplicationUserManager>();เพื่อรีเซ็ตรหัสผ่านของคุณใช้ หากคุณไม่มี OwinContext เดียวกันคุณต้องสร้างใหม่DataProtectorTokenProviderเหมือนที่OwinContextใช้ App_Start -> IdentityConfig.csโดยดูที่ค่าเริ่มต้น ควรมีลักษณะnew DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));ดังนี้

สามารถสร้างได้ดังนี้:

หากไม่มี Owin:

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

กับ Owin:

[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 Identity:

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

}

"สิ่งที่เหมาะกับฉัน" นั้นไม่ดีพอสำหรับสิ่งที่เกี่ยวข้องกับความปลอดภัย ฉันต้องการใช้. NET Core ที่สร้างไว้ล่วงหน้าให้มากที่สุด
Heinzlmaen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.