ASP.NET Core Identity如何强制未确认邮箱的已认证用户跳转至确认页?
处理ASP.NET Core Identity中已认证但未确认邮箱用户的跳转问题
你提到的场景确实是Identity的常见需求,官方没有直接提供EmailConfirmationPath这类配置,但可以通过以下几种标准方式实现:
一、自定义授权策略(推荐,贴合Identity授权体系)
这种方式通过授权策略拦截未确认邮箱的已认证用户,比中间件更符合ASP.NET Core的授权设计:
- 创建自定义授权要求与处理程序
// 自定义授权要求 public class EmailConfirmedRequirement : IAuthorizationRequirement { } // 授权处理程序 public class EmailConfirmedAuthorizationHandler : AuthorizationHandler<EmailConfirmedRequirement> { private readonly UserManager<IdentityUser> _userManager; private readonly IHttpContextAccessor _httpContextAccessor; public EmailConfirmedAuthorizationHandler(UserManager<IdentityUser> userManager, IHttpContextAccessor httpContextAccessor) { _userManager = userManager; _httpContextAccessor = httpContextAccessor; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, EmailConfirmedRequirement requirement) { var user = await _userManager.GetUserAsync(_httpContextAccessor.HttpContext.User); if (user == null) { context.Fail(); return; } // 排除邮箱确认相关页面,避免死循环 var path = _httpContextAccessor.HttpContext.Request.Path; if (path.StartsWithSegments("/Account/ConfirmEmail") || path.StartsWithSegments("/Account/ResendConfirmationEmail")) { context.Succeed(requirement); return; } if (await _userManager.IsEmailConfirmedAsync(user)) { context.Succeed(requirement); } else { // 跳转至邮箱确认页 _httpContextAccessor.HttpContext.Response.Redirect("/Account/ConfirmEmail"); context.Succeed(requirement); // 标记为成功避免后续授权拦截 } } }
- 注册授权策略
在Program.cs中添加:
builder.Services.AddAuthorization(options => { options.AddPolicy("RequireEmailConfirmed", policy => policy.Requirements.Add(new EmailConfirmedRequirement())); }); // 注册授权处理程序 builder.Services.AddScoped<IAuthorizationHandler, EmailConfirmedAuthorizationHandler>();
- 应用策略
- 全局应用(所有路由生效,除了排除的页面):
app.UseAuthorization(); app.MapControllers().RequireAuthorization("RequireEmailConfirmed");
- 或针对特定控制器/Action添加
[Authorize(Policy = "RequireEmailConfirmed")]
二、自定义中间件(灵活直接)
这是你最初的思路,优化后避免死循环即可:
public class EmailConfirmationMiddleware { private readonly RequestDelegate _next; public EmailConfirmationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) { if (signInManager.IsSignedIn(context.User)) { var user = await userManager.GetUserAsync(context.User); if (user != null && !await userManager.IsEmailConfirmedAsync(user)) { // 排除邮箱确认相关路径 var path = context.Request.Path.Value; if (!path.Contains("/Account/ConfirmEmail") && !path.Contains("/Account/ResendConfirmationEmail")) { context.Response.Redirect("/Account/ConfirmEmail"); return; } } } await _next(context); } } // 注册中间件(放在UseAuthentication和UseAuthorization之间) app.UseAuthentication(); app.UseMiddleware<EmailConfirmationMiddleware>(); app.UseAuthorization();
三、登录后即时跳转(补充方案)
在登录逻辑中,验证用户登录成功后直接检查邮箱状态,跳转至确认页:
public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { var user = await _userManager.FindByEmailAsync(model.Email); if (!await _userManager.IsEmailConfirmedAsync(user)) { return RedirectToAction("ConfirmEmail", "Account"); } return RedirectToLocal(model.ReturnUrl); } // 其他登录失败逻辑 } return View(model); }
注意事项
- 务必确保邮箱确认页面和重发邮件的接口不被拦截,否则会出现无限跳转的死循环。
- 重发邮件的接口需要允许未确认邮箱的用户访问,让用户可以重新获取确认令牌。
内容的提问来源于stack exchange,提问作者IvanD
相关产品推荐
相关产品推荐

