Toggle navigation
首页
技术
骑行
羽毛球
资讯
联络我
登录
ASP.NET Core 用户注册启用Email认证时需要注意的问题
2017-05-24
.NET Core
# 用户注册启用Email认证 > 此步骤可以参考微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/accconfirm Asp.Net core默认生成的带账号认证的Register代码模板,默认是没有启用Email验证功能,代码如下: ```c# public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713 // Send an email with this link //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); //var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); //await _emailSender.SendEmailAsync(model.Email, "Confirm your account", // $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>"); await _signInManager.SignInAsync(user, isPersistent: false); _logger.LogInformation(3, "User created a new account with password."); return RedirectToLocal(returnUrl); } AddErrors(result); } // If we got this far, something failed, redisplay form return View(model); } ``` 想要启用Email认证,要做如下几件事情: 1. 调整Register方法的注册逻辑,将登陆替换为发送验证Email 2. 在startup类中声明,Email没有经过验证的账号不能登陆 3. 使用自己的邮件服务或者注册在线的邮件服务,发送Email ## 1. 调整Register方法 ```c# if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713 // Send an email with this link var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); await _emailSender.SendEmailAsync(model.Email, "Confirm your account", // $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>"); //await _signInManager.SignInAsync(user, isPersistent: false); //_logger.LogInformation(3, "User created a new account with password."); //return RedirectToLocal(returnUrl); ViewData["RegisterMessage"] = "请登录您的邮箱验证账号!"; return View(model); } ``` ## 2. 设置Email没有经过验证的账号不能登陆 ```c# public void ConfigureServices(IServiceCollection services) { services.Configure<IdentityOptions>(options => { options.SignIn.RequireConfirmedEmail = true; }); ``` ## 3. 使用邮件服务 可以参照微软官方文档: https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/accconfirm # 潜在的问题 先看看默认验证Email code的代码: ```c# public async Task<IActionResult> ConfirmEmail(string userId, string code) { if (userId == null || code == null) { return View("Error"); } var user = await _userManager.FindByIdAsync(userId); if (user == null) { return View("Error"); } var result = await _userManager.ConfirmEmailAsync(user, code); return View(result.Succeeded ? "ConfirmEmail" : "Error"); } ``` 这里并没有判断 code 是否已经被验证过,只是单纯的验证 code 是否和 _userManager.GenerateEmailConfirmationTokenAsync(user) 生成的 code 对应起来。 这会有什么问题? 如果有人能够拿到这个code,无需密码即可以该账号的身份登录系统。 # 如何防止此问题的发生? 有如下两种方法: 1. 在 ConfirmEmail 方法中,判断 user 的email是否已经处于验证通过状态,如果是则返回错误。 2. 在 ConfirmEmail 方法中,user email 首次验证通过之后,生成新的 code,让之前验证的 code作废。 ## 1. 判断 user 的email是否已经处于验证通过状态 ```c# public async Task<IActionResult> ConfirmEmail(string userId, string code) { if (userId == null || code == null) { return View("Error"); } var user = await _userManager.FindByIdAsync(userId); if (user == null) { return View("Error"); } // add for blocking old email validation code. if (user.EmailConfirmed) { ViewData["ErrorMessage"] = "验证码已经过期!"; return View(); } var result = await _userManager.ConfirmEmailAsync(user, code); return View(result.Succeeded ? "ConfirmEmail" : "Error"); } ``` ## 2. 生成新的 code 如果仅仅是调用 _userManager.GenerateEmailConfirmationTokenAsync(user) 方法,会发现无论调用多少次,返回的code均不会变化。 查看ASP.Net core的源码可以看到,此方法生成的code依赖于user的 SecurityStamp 属性: > https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/DataProtectionTokenProvider.cs#L82 ```c# public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } var ms = new MemoryStream(); var userId = await manager.GetUserIdAsync(user); using (var writer = ms.CreateWriter()) { writer.Write(DateTimeOffset.UtcNow); writer.Write(userId); writer.Write(purpose ?? ""); string stamp = null; if (manager.SupportsUserSecurityStamp) { stamp = await manager.GetSecurityStampAsync(user); } writer.Write(stamp ?? ""); } var protectedBytes = Protector.Protect(ms.ToArray()); return Convert.ToBase64String(protectedBytes); } ``` SecurityStamp不会变化,除非 a users credentials change (password changed, login removed) 可以呼叫 _userManager.UpdateSecurityStampAsync(user); 方法来改变 user 的 SecurityStamp。 # 更多的应用场景 通常情况下,站点会提供“重新发送验证Email”功能,利用上面生成新的 code 的方法,可以保证每次发送的验证Email code不同,并且让旧的code失效,保证安全性。
×
本文为博主原创,如需转载,请注明出处:
http://www.supperxin.com
返回博客列表