.net core identity 集成 github (一)
首先 nuget 需要引用相关的包
EF
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
支持延迟加载
Microsoft.EntityFrameworkCore.Proxies
Identity
Microsoft.AspNetCore.Identity
Microsoft.AspNetCore.Identity.EntityFrameworkCore
集成GitHub
AspNet.Security.OAuth.GitHub
配置GitHub 应用 ClientID ClientSecrets
登录GitHub


按自己的需要配置

配置完 就有 ClientID ClientSecrets
appsettings.json GitHub配置
1 "Authentication": { 2 "GitHub": { 3 "AppID": "你的ID", 4 "ClientID": "你的ClientID", 5 "ClientSecrets": "你的ClientSecrets" 6 7 },
核心代码
1 //身份验证配置 2 builder.Services.AddAuthentication(options => 3 { 4 //cookie解决方案 5 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; 6 7 }).AddCookie(options => 8 { 9 //过期时间 60分钟 10 options.ExpireTimeSpan = TimeSpan.FromMinutes(60); 11 //开启滑动过期 12 options.SlidingExpiration = true; 13 }).AddGitHub(options => 14 { 15 //集成GitHub 16 options.ClientId = builder.Configuration["Authentication:GitHub:ClientID"]; 17 options.ClientSecret = builder.Configuration["Authentication:GitHub:ClientSecrets"]; 18 //读取 email 默认没有设置读取不到 19 options.Scope.Add("user:email"); 20 }); 21 //数据库设置和注入 22 builder.Services.AddDbContext<ShopAppContext>(options => 23 { 24 string con = builder.Configuration.GetConnectionString("con"); 25 options.UseLazyLoadingProxies(); 26 options.UseSqlServer(con); 27 }); 28 //identity设置 29 builder.Services.Configure<IdentityOptions>(options => 30 { 31 //最多失误次数锁定 32 options.Lockout.MaxFailedAccessAttempts = 5; 33 //每次锁定15分钟 34 options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15); 35 //登陆必须是邮件确认的 36 options.SignIn.RequireConfirmedEmail = true; 37 38 //自定义邮箱token生成提供程序 39 options.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation"; 40 41 //密码设置 42 options.Password.RequiredLength = 6; 43 options.Password.RequiredUniqueChars = 3; 44 options.Password.RequireNonAlphanumeric = false; 45 options.Password.RequireLowercase = false; 46 options.Password.RequireUppercase = false; 47 options.Password.RequireDigit = false; 48 49 }); 50 //identity注入 51 builder.Services.AddIdentity<ApplicationUser, IdentityRole>() 52 .AddErrorDescriber<CustomIdentityErrorDescriber>() 53 .AddEntityFrameworkStores<ShopAppContext>() 54 .AddDefaultTokenProviders() 55 // .AddTokenProvider<CustomEmailConfirmationTokenProvider<ApplicationUser>>("CustomEmailConfirmation") 56 ;
AccountController
1 public class AccountController : Controller 2 { 3 private readonly SignInManager<ApplicationUser> _signInManager; 4 private readonly UserManager<ApplicationUser> _userManager; 5 private readonly ILogger<AccountController> _logger; 6 public AccountController(UserManager<ApplicationUser> userManager, 7 SignInManager<ApplicationUser> signInManager, 8 ILogger<AccountController> logger) 9 { 10 _userManager = userManager; 11 _signInManager = signInManager; 12 _logger = logger; 13 } 14 15 [HttpGet] 16 public IActionResult Register() 17 { 18 return View(); 19 } 20 21 22 [HttpPost] 23 public async Task<IActionResult> RegisterAsync(RegisterViewModel viewModel) 24 { 25 if (ModelState.IsValid) 26 { 27 ApplicationUser user = await this._userManager.FindByNameAsync(viewModel.Email); 28 if (user != null) 29 { 30 ModelState.AddModelError("error", $"Email:{viewModel.Email},已注册!"); 31 return View(); 32 } 33 else 34 { 35 user = new ApplicationUser 36 { 37 UserName = viewModel.Email, 38 Email = viewModel.Email, 39 }; 40 var result = await _userManager.CreateAsync(user, viewModel.Password); 41 if (result.Succeeded) 42 { 43 44 string token = await this._userManager.GenerateEmailConfirmationTokenAsync(user); 45 string confirmEmailLink = Url.Action("ConfirmEmail", "Account", new { userID = user.Id, token = token }, Request.Scheme); 46 this._logger.Log(LogLevel.Error, confirmEmailLink); 47 ViewBag.ErrorTitle = "注册成功"; 48 ViewBag.ErrorMessage = $"在你登入系统前,我们已经给您发了一份邮件,需要您先进行邮件验证,点击确认链接即可完成。"; 49 return View("Error"); 50 //await _signInManager.SignInAsync(user, isPersistent: false); 51 //bool s = _signInManager.IsSignedIn(User); 52 //return RedirectToAction("Index", "Home"); 53 } 54 foreach (var error in result.Errors) 55 { 56 ModelState.AddModelError("error", error.Description); 57 } 58 } 59 } 60 return View(); 61 } 62 63 64 65 66 [HttpGet] 67 public async Task<IActionResult> LogoutAsync() 68 { 69 await _signInManager.SignOutAsync(); 70 return RedirectToAction("Index", "Home"); 71 } 72 73 74 [HttpGet] 75 public async Task<IActionResult> LoginAsync(string ReturlUrl) 76 { 77 LoginViewModel loginViewModel = new LoginViewModel 78 { 79 ReturnUrl = ReturlUrl, 80 ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList() 81 }; 82 83 return View(loginViewModel); 84 } 85 86 87 //[HttpPost] 88 //public async Task<IActionResult> LoginAsync(LoginViewModel viewModel) 89 //{ 90 // if (ModelState.IsValid) 91 // { 92 // var result = await _signInManager.PasswordSignInAsync(viewModel.Email, viewModel.Password, viewModel.RememberMe, true); 93 // if (result.Succeeded) 94 // { 95 // return RedirectToAction("Index", "Home"); 96 // } 97 // else 98 // { 99 // ModelState.AddModelError("error", "登录失败请重试"); 100 // } 101 // } 102 // return View(viewModel); 103 //} 104 105 [HttpPost] 106 [AllowAnonymous] 107 public async Task<IActionResult> LoginAsync(LoginViewModel viewModel, string? ReturlUrl) 108 { 109 viewModel.ExternalLogins = (await this._signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 110 if (ModelState.IsValid) 111 { 112 ApplicationUser applicationUser = new ApplicationUser 113 { 114 UserName = viewModel.Email, 115 Email = viewModel.Email, 116 }; 117 ApplicationUser resultUser = await this._userManager.FindByEmailAsync(viewModel.Email); 118 bool checkPasswordResult = await this._userManager.CheckPasswordAsync(applicationUser, viewModel.Password); 119 //1:通过邮箱查找的用户不能为空2:验证密码用户是否存在3:邮箱是否确认 120 if (resultUser != null && !resultUser.EmailConfirmed && checkPasswordResult) 121 { 122 ModelState.AddModelError("error", "邮箱未确认"); 123 return View(viewModel); 124 } 125 //开启锁定 126 var signInResult = await this._signInManager.PasswordSignInAsync(resultUser, 127 viewModel.Password, 128 viewModel.RememberMe, true); 129 if (signInResult.Succeeded) 130 { 131 if (!string.IsNullOrEmpty(ReturlUrl)) 132 { 133 LocalRedirect(ReturlUrl); 134 } 135 else 136 { 137 return RedirectToAction("Index", "Home"); 138 } 139 } 140 141 142 143 //如果用户已经锁定了 144 if (signInResult.IsLockedOut) 145 { 146 return View("AccountLocked"); 147 } 148 149 150 ModelState.AddModelError("error", "登陆失败"); 151 } 152 return View(viewModel); 153 } 154 155 156 /// <summary> 157 /// 外部登录 158 /// </summary> 159 /// <param name="provider"></param> 160 /// <param name="returnUrl"></param> 161 /// <returns></returns> 162 [HttpPost] 163 public IActionResult ExternalLogin(string provider, string returnUrl) 164 { 165 var redirectUrl = Url.Action("ExternalLoginCallback", "Account", 166 new { ReturnUrl = returnUrl }); 167 var properties = _signInManager 168 .ConfigureExternalAuthenticationProperties(provider, redirectUrl); 169 return new ChallengeResult(provider, properties); 170 } 171 172 /// <summary> 173 /// 外部登录回调 174 /// </summary> 175 /// <param name="returnUrl"></param> 176 /// <param name="remoteError"></param> 177 /// <returns></returns> 178 /* public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) 179 { 180 returnUrl = returnUrl ?? Url.Content("~/"); 181 LoginViewModel loginViewModel = new LoginViewModel 182 { 183 ReturnUrl = returnUrl, 184 ExternalLogins = (await this._signInManager.GetExternalAuthenticationSchemesAsync()).ToList() 185 }; 186 ExternalLoginInfo info = await this._signInManager.GetExternalLoginInfoAsync(); 187 //获取第三方用户的邮箱 188 string email = info.Principal.FindFirstValue(ClaimTypes.Email); 189 if (!string.IsNullOrEmpty(email)) 190 { 191 192 //查找第三方用户的邮箱 本地数据库中是否有该用户 193 ApplicationUser user = await this._userManager.FindByEmailAsync(email); 194 //如果该用户邮箱没有确认 195 if (user != null && !user.EmailConfirmed) 196 //if (user != null) 197 { 198 ModelState.AddModelError(string.Empty, "您的电子邮箱还未进行验证。"); 199 return View("Login", loginViewModel); 200 } 201 } 202 var signResult = await this._signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, false, bypassTwoFactor: true); 203 if (signResult.Succeeded) 204 { 205 //如果登录了就不用创建用户 直接跳转 206 return LocalRedirect(returnUrl); 207 } 208 else 209 { 210 ApplicationUser applicationUser = new ApplicationUser 211 { 212 UserName = info.Principal.FindFirstValue(ClaimTypes.Email), 213 Email = info.Principal.FindFirstValue(ClaimTypes.Email) 214 }; 215 var createResult = await this._userManager.CreateAsync(applicationUser); 216 if (createResult.Succeeded) 217 { 218 await this._userManager.AddLoginAsync(applicationUser, info); 219 await this._signInManager.SignInAsync(applicationUser, isPersistent: false); 220 } 221 return RedirectToAction("Index", "Home"); 222 } 223 ViewBag.ErrorTitle = $"我们无法从提供商:{info.LoginProvider}中解析到您的邮件地址 "; 224 ViewBag.ErrorMessage = "请通过联系 ltm@ddxc.org 寻求技术支持。"; 225 return View("Error"); 226 } 227 */ 228 229 230 /// <summary> 231 /// 外部登录回调 232 /// </summary> 233 /// <param name="returnUrl"></param> 234 /// <param name="remoteError"></param> 235 /// <returns></returns> 236 public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) 237 { 238 returnUrl = returnUrl ?? Url.Content("~/"); 239 var externalInfo = await this._signInManager.GetExternalLoginInfoAsync(); 240 string externalEmail = externalInfo.Principal.FindFirstValue(ClaimTypes.Email); 241 ApplicationUser applicationUser = null; 242 if (externalEmail != null) 243 { 244 //如果 第三方邮箱不等于空 我要判断该邮箱在本地是否确认过 245 applicationUser = await this._userManager.FindByEmailAsync(externalEmail); 246 if (applicationUser != null && !applicationUser.EmailConfirmed) 247 { 248 //用户不等于空 并且邮箱没有确认 249 ModelState.AddModelError(string.Empty, "您的电子邮箱还未进行验证。"); 250 LoginViewModel loginViewModel = new LoginViewModel 251 { 252 ExternalLogins = (await this._signInManager.GetExternalAuthenticationSchemesAsync()).ToList(), 253 ReturnUrl = returnUrl 254 }; 255 return View("Login", loginViewModel); 256 } 257 } 258 //如果邮箱确认过 我需要判断 该用户否有已经登陆 259 var externalLoginSignResult = await this._signInManager 260 .ExternalLoginSignInAsync(externalInfo.LoginProvider, 261 externalInfo.ProviderKey, false, true); 262 if (externalLoginSignResult.Succeeded) 263 { 264 return LocalRedirect(returnUrl); 265 } 266 else 267 { 268 if (externalEmail != null) 269 { 270 if (applicationUser == null) 271 { 272 applicationUser = new ApplicationUser 273 { 274 UserName = externalInfo.Principal.FindFirstValue(ClaimTypes.Email), 275 Email = externalInfo.Principal.FindFirstValue(ClaimTypes.Email), 276 }; 277 await this._userManager.CreateAsync(applicationUser); 278 string token = await this._userManager.GenerateEmailConfirmationTokenAsync(applicationUser); 279 280 string confirmationLink = Url.Action("ConfirmEmail", "Account", 281 new { userID = applicationUser.Id, token = token }, Request.Scheme); 282 _logger.Log(LogLevel.Warning, confirmationLink); 283 ViewBag.ErrorTitle = "注册成功"; 284 ViewBag.ErrorMessage = $"在你登入系统前,我们已经给您发了一份邮件,需要您先进行邮件验证,点击确认链接即可完成。"; 285 return View("Error"); 286 } 287 288 //如果 applicationUser 不等于空 我就直接登陆 289 await this._userManager.AddLoginAsync(applicationUser, externalInfo); 290 await this._signInManager.SignInAsync(applicationUser, false); 291 return LocalRedirect(returnUrl); 292 } 293 // 如果我们获取不到电子邮件地址,我们需要将请求重定向到错误视图中。 294 ViewBag.ErrorTitle = $"我们无法从提供商:{externalInfo.LoginProvider}中解析到您的邮件地址 "; 295 ViewBag.ErrorMessage = "请通过联系 ltm@ddxc.org 寻求技术支持。"; 296 return View("Error"); 297 } 298 299 } 300 301 302 /// <summary> 303 /// 确认邮件 304 /// </summary> 305 /// <param name="userID"></param> 306 /// <param name="token"></param> 307 /// <returns></returns> 308 [HttpGet] 309 public async Task<IActionResult> ConfirmEmailAsync(string userID, string token) 310 { 311 if (string.IsNullOrEmpty(userID) && string.IsNullOrEmpty(token)) 312 { 313 return RedirectToAction("Index", "Home"); 314 } 315 316 var user = await this._userManager.FindByIdAsync(userID); 317 if (user == null) 318 { 319 ViewBag.ErrorTitle = $"当前id{userID}无效"; 320 ViewBag.ErrorMessage = "请通过联系 ltm@ddxc.org 寻求技术支持。"; 321 return View("Error"); 322 } 323 324 var result = await this._userManager.ConfirmEmailAsync(user, token); 325 if (result.Succeeded) 326 { 327 return View(); 328 } 329 else 330 { 331 ViewBag.ErrorTitle = $"邮箱验证失败"; 332 ViewBag.ErrorMessage = $"邮箱验证失败"; 333 return View("Error"); 334 } 335 } 336 337 338 339 /// <summary> 340 /// 拒绝访问 341 /// </summary> 342 /// <param name="ReturnUrl"></param> 343 /// <returns></returns> 344 [HttpGet] 345 public IActionResult AccessDenied(string ReturnUrl) 346 { 347 //防止开放式攻击 348 if (!string.IsNullOrEmpty(ReturnUrl) && Url.IsLocalUrl(ReturnUrl)) 349 { 350 if (ReturnUrl.Contains("Back")) 351 { 352 return RedirectToAction("Index", "Home"); 353 } 354 else 355 { 356 return Redirect(ReturnUrl); 357 } 358 } 359 else 360 { 361 return RedirectToAction("Index", "Home"); 362 } 363 } 364 365 366 #region 激活用户 367 [HttpGet] 368 public IActionResult ActivateUserEmail() 369 { 370 return View(); 371 } 372 373 374 [HttpPost] 375 public async Task<IActionResult> ActivateUserEmail(EmailAddressViewModel emailAddress) 376 { 377 if (ModelState.IsValid) 378 { 379 var user = await this._userManager.FindByEmailAsync(emailAddress.Email); 380 if (user != null) 381 { 382 bool isConfirmedResult = await this._userManager.IsEmailConfirmedAsync(user); 383 if (!isConfirmedResult) 384 { 385 string token = await this._userManager.GenerateEmailConfirmationTokenAsync(user); 386 387 string urlStr = Url.Action("ConfirmEmail", "Acccount", new { userID = user.Id, token = token }, Request.Scheme); 388 389 ViewBag.Message = "如果你在我们系统有注册账户,我们已经发了邮件到您的邮箱中,请前往邮箱激活您的用户。"; 390 //重定向用户到忘记密码确认视图 391 return View("ActivateUserEmailConfirmation", ViewBag.Message); 392 } 393 } 394 } 395 396 ViewBag.Message = "请确认邮箱是否存在异常,现在我们无法给您发送激活链接。"; 397 // 为了避免帐户枚举和暴力攻击,所以不进行用户不存在或邮箱未验证的提示 398 return View("ActivateUserEmailConfirmation", ViewBag.Message); 399 } 400 #endregion 401 402 #region 忘记密码功能 403 404 [HttpGet] 405 406 public IActionResult ForgotPassword() 407 { 408 return View(); 409 } 410 411 [HttpPost] 412 413 public async Task<IActionResult> ForgotPassword(EmailAddressViewModel emailAddress) 414 { 415 if (ModelState.IsValid) 416 { 417 var user = await this._userManager.FindByEmailAsync(emailAddress.Email); 418 if (user != null && user.EmailConfirmed) 419 { 420 string token = await this._userManager.GeneratePasswordResetTokenAsync(user); 421 string passwordResetLink = Url.Action("ResetPassword", "Acccount", new { userID = user.Id, token = token }, Request.Scheme); 422 // 将密码重置链接记录到文件中 423 _logger.Log(LogLevel.Warning, passwordResetLink); 424 425 //重定向用户到忘记密码确认视图 426 return View("ForgotPasswordConfirmation"); 427 } 428 // 为了避免帐户枚举和暴力攻击,所以不进行用户不存在或邮箱未验证的提示 429 return View("ForgotPasswordConfirmation"); 430 } 431 return View(emailAddress); 432 } 433 434 435 [HttpGet] 436 public IActionResult ResetPassword(string token, string email) 437 { 438 439 //如果密码重置令牌或电子邮件为空,则有可能是用户在试图篡改密码重置链接 440 if (token == null || email == null) 441 { 442 ModelState.AddModelError("", "无效的密码重置令牌"); 443 } 444 return View(); 445 } 446 [HttpPost] 447 public async Task<IActionResult> ResetPassword(ResetPasswordViewModel viewModel) 448 { 449 450 if (ModelState.IsValid) 451 { 452 var user = await this._userManager.FindByEmailAsync(viewModel.Email); 453 if (user != null) 454 { 455 var result = await this._userManager.ResetPasswordAsync(user, viewModel.Token, viewModel.ConfirmPassword); 456 457 if (result.Succeeded) 458 { 459 if (await this._userManager.IsLockedOutAsync(user)) 460 { 461 //重置 锁定时间 462 await this._userManager.SetLockoutEndDateAsync(user, DateTime.UtcNow); 463 //重置 锁定次数 464 await this._userManager.ResetAccessFailedCountAsync(user); 465 } 466 return View("ResetPasswordConfirmation"); 467 } 468 //显示验证错误信息。触发行为,当密码重置令牌已用,或密码复杂性规则不符合标准时 469 foreach (var error in result.Errors) 470 { 471 ModelState.AddModelError("", error.Description); 472 } 473 return View(viewModel); 474 } 475 // 为了避免帐户枚举和暴力攻击,不要提示用户不存在 476 return View("ResetPasswordConfirmation"); 477 } 478 479 480 return View(viewModel); 481 } 482 483 484 #endregion 485 }
Login.cshtml 视图
1 @using Shop.ViewModels 2 @model LoginViewModel 3 <h4>登录</h4> 4 <form class="form-horizontal" method="post"> 5 <div class="alert" asp-validation-summary="All"></div> 6 <div class="control-group"> 7 <label class="control-label" asp-for="Email">邮箱</label> 8 <div class="controls"> 9 <input type="text" asp-for="Email" placeholder="Email"> 10 </div> 11 </div> 12 <div class="control-group"> 13 <label class="control-label" asp-for="Password">密码</label> 14 <div class="controls"> 15 <input type="password" asp-for="Password" placeholder="Password"> 16 </div> 17 </div> 18 <div class="control-group"> 19 <div class="controls"> 20 <label class="checkbox"> 21 <input type="checkbox" asp-for="RememberMe">记住我 22 </label> 23 <button type="submit" class="btn">登录</button> 24 </div> 25 </div> 26 </form> 27 28 <div class="col-md-6 border-left "> 29 <h1>扩展登录</h1><hr /> 30 <form class="mt-3" method="post" asp-action="ExternalLogin" asp-controller="Account" asp-route-returnUrl="@Model.ReturnUrl"> 31 <div> 32 33 @foreach (var provider in Model.ExternalLogins) 34 { 35 <button type="submit" class="btn btn-info" 36 name="provider" value="@provider.Name" 37 title="Log in using your @provider.DisplayName account"> 38 @provider.DisplayName 39 </button> 40 } 41 </div> 42 </form> 43 </div>
Register.cshtml 视图
1 @using Shop.ViewModels 2 @model RegisterViewModel 3 @{ 4 ViewBag.Title = "注册"; 5 } 6 7 8 <h1>用户注册</h1> 9 10 <div class="row"> 11 <div class="col-md-12"> 12 <form method="post"> 13 <div asp-validation-summary="All" class="text-danger"></div> 14 <div class="form-group"> 15 <label asp-for="Email"></label> 16 <input asp-for="Email" class="form-control" /> 17 <span asp-validation-for="Email" class="text-danger"></span> 18 </div> 19 <div class="form-group"> 20 <label asp-for="Password"></label> 21 <input asp-for="Password" class="form-control" /> 22 <span asp-validation-for="Password" class="text-danger"></span> 23 </div> 24 <div class="form-group"> 25 <label asp-for="ConfirmPassword"></label> 26 <input asp-for="ConfirmPassword" class="form-control" /> 27 <span asp-validation-for="ConfirmPassword" class="text-danger"></span> 28 </div> 29 <button type="submit" class="btn btn-primary">注册</button> 30 </form> 31 </div> 32 </div>
ResetPassword.cshtml 视图
1 @using Shop.ViewModels 2 @model ResetPasswordViewModel 3 4 <h2>重置密码</h2> 5 <hr /> 6 <div class="row"> 7 <div class="col-md-12"> 8 <form method="post"> 9 <div asp-validation-summary="All" class="text-danger"></div> 10 <input asp-for="Token" type="hidden" /> 11 <input asp-for="Email" type="hidden" /> 12 <div class="form-group"> 13 <label asp-for="Password"></label> 14 <input asp-for="Password" class="form-control" /> 15 <span asp-validation-for="Password" class="text-danger"></span> 16 </div> 17 <div class="form-group"> 18 <label asp-for="ConfirmPassword"></label> 19 <input asp-for="ConfirmPassword" class="form-control" /> 20 <span asp-validation-for="ConfirmPassword" class="text-danger"></span> 21 </div> 22 <button type="submit" class="btn btn-primary">重置</button> 23 </form> 24 </div> 25 </div>
ResetPasswordConfirmation.cshtml 视图
1 @using Shop.ViewModels 2 @model ResetPasswordViewModel 3 4 <h2>重置密码</h2> 5 <hr /> 6 <div class="row"> 7 <div class="col-md-12"> 8 <form method="post"> 9 <div asp-validation-summary="All" class="text-danger"></div> 10 <input asp-for="Token" type="hidden" /> 11 <input asp-for="Email" type="hidden" /> 12 <div class="form-group"> 13 <label asp-for="Password"></label> 14 <input asp-for="Password" class="form-control" /> 15 <span asp-validation-for="Password" class="text-danger"></span> 16 </div> 17 <div class="form-group"> 18 <label asp-for="ConfirmPassword"></label> 19 <input asp-for="ConfirmPassword" class="form-control" /> 20 <span asp-validation-for="ConfirmPassword" class="text-danger"></span> 21 </div> 22 <button type="submit" class="btn btn-primary">重置</button> 23 </form> 24 </div> 25 </div>
AccountLocked.cshtml 视图
1 <label class="error">账户被锁定</label> 2 3 <a asp-action="ForgotPassword" asp-controller="Account">点击重置密码</a>
ActivateUserEmail.cshtml 视图
1 @using Shop.ViewModels 2 @model EmailAddressViewModel 3 4 <h2>激活邮箱</h2> 5 <hr /> 6 <div class="row"> 7 <div class="col-md-12"> 8 <form method="post"> 9 <div asp-validation-summary="All" class="text-danger"></div> 10 <div class="form-group"> 11 <label asp-for="Email"></label> 12 <input asp-for="Email" class="form-control" /> 13 <span asp-validation-for="Email" class="text-danger"></span> 14 </div> 15 <button type="submit" class="btn btn-primary">提交</button> 16 </form> 17 </div> 18 </div>
ActivateUserEmailConfirmation.cshtml 视图
1 <h1 class="text-info">邮件发送通知</h1> 2 <h4 class="text-secondary"> 3 @ViewBag.Message 4 5 </h4>
ForgotPassword.cshtml 视图 这个没写
1 @{ 2 }
测试


邮箱验证

验证成功集成github用户

浙公网安备 33010602011771号