【问题标题】:.NET Identity Email/Username change.NET 身份电子邮件/用户名更改
【发布时间】:2014-10-23 13:38:55
【问题描述】:

有谁知道如何使用户能够通过电子邮件确认更改具有 ASP.NET 身份的用户名/电子邮件?有很多关于如何更改密码的示例,但我找不到任何内容。

【问题讨论】:

    标签: asp.net asp.net-identity


    【解决方案1】:

    2017 年 12 月更新 在 cmets 中提出了一些优点:

    • 在确认新电子邮件时最好有一个单独的字段 - 以防用户输入了错误的电子邮件。等到新电子邮件得到确认,然后将其设为主电子邮件。请参阅下面 Chris_ 的非常详细的回答。
    • 还可能存在使用该电子邮件的帐户已经存在的情况 - 确保您也检查了这一点,否则可能会出现问题。

    这是一个非常基本的解决方案,并未涵盖所有可能的组合,因此请使用您的判断并确保您通读了 cmets - 那里提出了非常好的观点。

    // get user object from the storage
    var user = await userManager.FindByIdAsync(userId);
    
    // change username and email
    user.Username = "NewUsername";
    user.Email = "New@email.com";
    
    // Persiste the changes
    await userManager.UpdateAsync(user);
    
    // generage email confirmation code
    var emailConfirmationCode = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
    
    // generate url for page where you can confirm the email
    var callbackurl= "http://example.com/ConfirmEmail";
    
    // append userId and confirmation code as parameters to the url
    callbackurl += String.Format("?userId={0}&code={1}", user.Id, HttpUtility.UrlEncode(emailConfirmationCode));
    
    var htmlContent = String.Format(
            @"Thank you for updating your email. Please confirm the email by clicking this link: 
            <br><a href='{0}'>Confirm new email</a>",
            callbackurl);
    
    // send email to the user with the confirmation link
    await userManager.SendEmailAsync(user.Id, subject: "Email confirmation", body: htmlContent);
    
    
    
    // then this is the action to confirm the email on the user
    // link in the email should be pointing here
    public async Task<ActionResult> ConfirmEmail(string userId, string code)
    {
        var confirmResult = await userManager.ConfirmEmailAsync(userId, code);
    
        return RedirectToAction("Index");
    }
    

    【讨论】:

    • 很高兴您添加了 URL 编码,因为库存的 Microsoft AspNet 身份样本已损坏且不执行此操作。
    • 我建议您也注销用户,以便他们在重新确认电子邮件之前无法继续通过基于 cookie 的身份验证:stackoverflow.com/questions/25878218/…
    • 如果用户输入错误/不存在的电子邮件,这种方法会不会造成麻烦?我宁愿将新电子邮件存储在单独的字段中,并仅在确认完成后更新Email
    • 如果新的电子邮件地址错误并且用户名/电子邮件设置为该新电子邮件地址,那么用户将无法再登录,也无法单击确认链接......
    • @Simon_Weaver 好点。我在答案中添加了一个更新,说这不是一个完整的解决方案。
    【解决方案2】:

    Trailmax 大部分都是正确的,但正如 cmets 指出的那样,如果用户在更新时弄乱了新的电子邮件地址,他们基本上会陷入困境。

    要解决这个问题,有必要向您的用户类添加其他属性并修改登录。 (注意:这个答案将通过 MVC 5 项目解决)

    这是我拿它的地方:

    1.修改您的用户对象 首先,让我们更新应用程序用户以添加我们需要的附加字段。您将在 Models 文件夹的 IdentiyModel.cs 文件中添加它:

    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    
        [MaxLength(256)]
        public string UnConfirmedEmail { get; set; }//this is what we add
    
    }
    

    如果您想查看更深入的示例,请在此处查看http://blog.falafel.com/customize-mvc-5-application-users-using-asp-net-identity-2-0/(这是我使用的示例)

    此外,它没有在链接的文章中提到它,但您也需要更新您的 AspNetUsers 表:

    ALTER TABLE dbo.AspNetUsers
    ADD [UnConfirmedEmail] NVARCHAR(256) NULL;
    

    2。更新您的登录信息

    现在我们需要确保我们的登录也检查旧电子邮件确认,以便在我们等待用户确认这封新电子邮件时,事情可以“处于不确定状态”:

       //
        // POST: /Account/Login
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
    
            var allowPassOnEmailVerfication = false;
            var user = await UserManager.FindByEmailAsync(model.Email);
            if (user != null)
            {
                if (!string.IsNullOrWhiteSpace(user.UnConfirmedEmail))
                {
                    allowPassOnEmailVerfication = true;
                }
            }
    
    
            // This now counts login failures towards account lockout
            // To enable password failures to trigger account lockout, I changed to shouldLockout: true
            var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
            switch (result)
            {
                case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
                case SignInStatus.LockedOut:
                    return View("Lockout");
                case SignInStatus.RequiresVerification:
                    return allowPassOnEmailVerfication ? RedirectToLocal(returnUrl) : RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                case SignInStatus.Failure:
                default:
                    ModelState.AddModelError("", "Invalid login attempt.");
                    return View(model);
            }
        }
    

    就是这样......你基本上完成了!但是,我总是对那些没有让你越过以后可能会遇到的潜在陷阱的答案感到恼火,所以让我们继续我们的冒险,好吗?

    3.更新您的管理/索引

    在我们的 index.cshtml 中,让我们为电子邮件添加一个新部分。不过,在我们到达那里之前,让我们在 ManageViewmodel.cs 中添加我们需要的字段

    public class IndexViewModel
    {
        public bool HasPassword { get; set; }
        public IList<UserLoginInfo> Logins { get; set; }
        public string PhoneNumber { get; set; }
        public bool TwoFactor { get; set; }
        public bool BrowserRemembered { get; set; }
    
        public string ConfirmedEmail { get; set; } //add this
        public string UnConfirmedEmail { get; set; } //and this
    }
    

    在我们的 Manage 控制器中跳转到 index 操作,将其添加到我们的视图模型中:

            var userId = User.Identity.GetUserId();
            var currentUser = await UserManager.FindByIdAsync(userId);
    
            var unConfirmedEmail = "";
            if (!String.IsNullOrWhiteSpace(currentUser.UnConfirmedEmail))
            {
                unConfirmedEmail = currentUser.UnConfirmedEmail;
            }
            var model = new IndexViewModel
            {
                HasPassword = HasPassword(),
                PhoneNumber = await UserManager.GetPhoneNumberAsync(userId),
                TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId),
                Logins = await UserManager.GetLoginsAsync(userId),
                BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId),
                ConfirmedEmail = currentUser.Email,
                UnConfirmedEmail = unConfirmedEmail
            };
    

    最后,对于本节,我们可以更新索引以允许我们管理这个新的电子邮件选项:

    <dt>Email:</dt>
        <dd>
            @Model.ConfirmedEmail
            @if (!String.IsNullOrWhiteSpace(Model.UnConfirmedEmail))
            {
                <em> - Unconfirmed: @Model.UnConfirmedEmail </em> @Html.ActionLink("Cancel", "CancelUnconfirmedEmail",new {email=Model.ConfirmedEmail})
            }
            else
            {
                @Html.ActionLink("Change Email", "ChangeEmail")
            }
        </dd>
    

    4.添加这些新的修改

    首先,让我们添加 ChangeEmail:

    查看模型:

    public class ChangeEmailViewModel
    {
        public string ConfirmedEmail { get; set; } 
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        [DataType(DataType.EmailAddress)]
        public string UnConfirmedEmail { get; set; } 
    }
    

    采取行动:

     public ActionResult ChangeEmail()
        {
            var user = UserManager.FindById(User.Identity.GetUserId());
            var model = new ChangeEmailViewModel()
            {
                ConfirmedEmail = user.Email
            };
    
            return View(model);
        }
    

    查看:

    @model ProjectName.Models.ChangeEmailViewModel
    @{
    ViewBag.Title = "Change Email";
    }
    
    <h2>@ViewBag.Title.</h2>
    
    @using (Html.BeginForm("ChangeEmail", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
    {
        @Html.AntiForgeryToken()
        <h4>New Email Address:</h4>
        <hr />
        @Html.ValidationSummary("", new { @class = "text-danger" })
        @Html.HiddenFor(m=>m.ConfirmedEmail)
        <div class="form-group">
            @Html.LabelFor(m => m.UnConfirmedEmail, new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.UnConfirmedEmail, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" class="btn btn-default" value="Email Link" />
            </div>
        </div>
    }
    

    HttpPost 动作:

        [HttpPost]
        public async Task<ActionResult> ChangeEmail(ChangeEmailViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return RedirectToAction("ChangeEmail", "Manage");
            }
    
            var user = await UserManager.FindByEmailAsync(model.ConfirmedEmail);
            var userId = user.Id;
            if (user != null)
            {
                //doing a quick swap so we can send the appropriate confirmation email
                user.UnConfirmedEmail = user.Email;
                user.Email = model.UnConfirmedEmail;
                user.EmailConfirmed = false;
                var result = await UserManager.UpdateAsync(user);
    
                if (result.Succeeded)
                {
    
                    string callbackUrl =
                    await SendEmailConfirmationTokenAsync(userId, "Confirm your new email");
    
                    var tempUnconfirmed = user.Email;
                    user.Email = user.UnConfirmedEmail;
                    user.UnConfirmedEmail = tempUnconfirmed;
                    result = await UserManager.UpdateAsync(user);
    
                    callbackUrl = await SendEmailConfirmationWarningAsync(userId, "You email has been updated to: "+user.UnConfirmedEmail);
    
    
                }
            }
            return RedirectToAction("Index","Manage");
        }
    

    现在添加该警告:

        private async Task<string> SendEmailConfirmationWarningAsync(string userID, string subject)
        {
            string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
            var callbackUrl = Url.Action("ConfirmEmail", "Account",
               new { userId = userID, code = code }, protocol: Request.Url.Scheme);
            await UserManager.SendEmailAsync(userID, subject,
               "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
    
            return callbackUrl;
        }
    

    现在终于可以取消新的电子邮件地址了:

        public async Task<ActionResult> CancelUnconfirmedEmail(string emailOrUserId)
        {
            var user = await UserManager.FindByEmailAsync(emailOrUserId);
            if (user == null)
            {
                user = await UserManager.FindByIdAsync(emailOrUserId);
                if (user != null)
                {
                    user.UnConfirmedEmail = "";
                    user.EmailConfirmed = true;
                    var result = await UserManager.UpdateAsync(user);
                }
            }
            else
            {
                user.UnConfirmedEmail = "";
                user.EmailConfirmed = true;
                var result = await UserManager.UpdateAsync(user);
            }
            return RedirectToAction("Index", "Manage");
    
        }
    

    5.更新 ConfirmEmail(最后一步)

    经过所有这些来回,我们现在可以确认新电子邮件,这意味着我们应该同时删除旧电子邮件。

     var result = UserManager.ConfirmEmail(userId, code);
     if (result.Succeeded)
     {
    
         var user = UserManager.FindById(userId);
         if (!string.IsNullOrWhiteSpace(user.UnConfirmedEmail))
         {
             user.Email = user.UnConfirmedEmail;
             user.UserName = user.UnConfirmedEmail;
             user.UnConfirmedEmail = "";
    
             UserManager.Update(user);
         }
     }
    

    【讨论】:

    • 我们可以添加一个声明而不是添加一个额外的字段
    • 这是一个很好的完整答案,应该真正被接受。感谢您发布此内容非常有帮助。
    • 感谢@RichardMcKenna,很高兴您发现它有帮助。我总是在试图保持简短......但想提供尽可能多的细节之间发生冲突。
    • 好点@gldraphael,虽然我还没有掌握索赔......所以至少现在这是我的方式。
    • 为什么要保存新邮件?如果有人知道如何登录,并且他想将电子邮件更改为其他内容,请生成代码,发送到已知的电子邮件地址。创建邮箱更改页面,让用户填写新邮箱地址作为确认。
    【解决方案3】:

    我按照 Jonathan 的步骤创建了一个全新的 ASP.NET 项目以测试更改,并且工作起来非常有魅力。这是repository的链接

    【讨论】:

      【解决方案4】:

      尚未查看ChangeEmailOnIdentity2.0ASPNET,但您不能利用 UserName 和 Email 值通常匹配的事实吗?这允许您根据请求更改电子邮件列,然后在确认后更改用户名。

      这两个控制器似乎对我有用:

          [HttpPost]
          [ValidateAntiForgeryToken]
          public async Task<ActionResult> ChangeUserName(LoginViewModel model)
          {
              IdentityResult result = new IdentityResult();
              try
              {
                  if (ModelState.IsValid)
                  {
                      var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
      
                      SignInStatus verify = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, false, false);
      
                      if (verify != SignInStatus.Success)
                      {
                          ModelState.AddModelError("Password", "Incorrect password.");
                      }
                      else
                      {
                          if (model.Email != user.Email)
                          {
                              user.Email = model.Email;
                              user.EmailConfirmed = false;
      
                              // Persist the changes
                              result = await UserManager.UpdateAsync(user);
      
                              if (result.Succeeded)
                              {
                                  string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
                                  var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code }, protocol: Request.Url.Scheme);
                                  await UserManager.SendEmailAsync(user.Id, "Confirm your updated email", "Please confirm your email address by clicking <a href=\"" + callbackUrl + "\">this</a>");
      
                                  return RedirectToAction("Index", new { Message = ManageMessageId.ChangeUserNamePending });
                              }
                          }
                          else
                          {
                              ModelState.AddModelError("Email", "Address specified matches current setting.");
                          }
                      }
                  }
              }
              catch (Exception ex)
              {
                  result.Errors.Append(ex.Message);
              }
              AddErrors(result);
              return View(model);
          }
      
          [AllowAnonymous]
          public async Task<ActionResult> ConfirmEmail(string userId, string code)
          {
              if (userId == null || code == null)
              {
                  return View("Error");
              }
              var result = await UserManager.ConfirmEmailAsync(userId, code);
      
              if (result.Succeeded)
              {
                  var user = await UserManager.FindByIdAsync(userId);
                  if (user.Email != user.UserName)
                  {
                      // Set the message to the current values before changing
                      String message = $"Your email user name has been changed from {user.UserName} to {user.Email} now.";
      
                      user.UserName = user.Email;
                      result = await UserManager.UpdateAsync(user);
                      if (result.Succeeded)
                      {
                          ViewBag.Message = message;
      
                          AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
                      }
                      else
                      {
                          result.Errors.Append("Could not modify your user name.");
                          AddErrors(result);
      
                          return View("Error");
                      }
                  }
                  return View("ConfirmEmail");
              }
              else
              {
                  return View("Error");
              }
          }
      

      【讨论】:

        【解决方案5】:

        如果有人正在寻找使用 Asp.Net Core 的解决方案: 这里的事情要简单得多,请参阅 SO 上的这篇文章 AspNet Core Generate and Change Email Address

        【讨论】:

        • 这个答案如何解决这个 SO 帖子中提出的问题,用户最初可以输入无效的电子邮件地址,然后陷入困境?
        猜你喜欢
        • 1970-01-01
        • 2020-07-13
        • 1970-01-01
        • 1970-01-01
        • 2020-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多