.NET 身份电子邮件/用户名更改

Posted

技术标签:

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

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

【问题讨论】:

【参考方案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 的身份验证:***.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 帖子中提出的问题,用户最初可以输入无效的电子邮件地址,然后陷入困境?

以上是关于.NET 身份电子邮件/用户名更改的主要内容,如果未能解决你的问题,请参考以下文章

Asp.net 身份额外的用户信息,如电子邮件等

无需输入电子邮件即可重置密码(ASP.NET 身份)

从 Twitter API 获取用户的电子邮件以进行外部登录身份验证 ASP.NET MVC C#

ASP.net 身份框架 - 重新发送确认电子邮件

使用 UserManager 更改用户的电子邮件

监控 Firebase 身份验证对象属性更改?