ASP.NET Core 使用默认密码登录时如何全局强制跳转至改密页面
方案1:使用全局中间件实现(推荐)
中间件是ASP.NET Core请求管道的核心组成部分,所有请求都会按顺序经过中间件处理,无需在每个控制器重复编写校验逻辑,完全适配全局拦截的需求。
首先编写自定义中间件类:
public class ForceChangePasswordMiddleware { private readonly RequestDelegate _next; public ForceChangePasswordMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 配置白名单:放行登录页、修改密码页、静态资源等无需校验的路径 var requestPath = context.Request.Path.Value.ToLower(); var whiteListPaths = new[] { "/login", "/user/newpassword", "/css", "/js", "/lib" }; if (whiteListPaths.Any(p => requestPath.StartsWith(p)) || !context.User.Identity.IsAuthenticated) { await _next(context); return; } // 从用户Claims中读取是否为默认密码的标记(推荐将标记存在Claims中,无需依赖Session或重复查库) var isDefaultPasswordClaim = context.User.Claims.FirstOrDefault(c => c.Type == "IsDefaultPassword"); if (isDefaultPasswordClaim != null && bool.TryParse(isDefaultPasswordClaim.Value, out var isDefault) && isDefault) { context.Response.Redirect("/User/NewPassword"); return; } await _next(context); } }
在Program.cs中注册中间件,注意注册顺序必须放在身份验证中间件之后、授权中间件之前:
// 先注册身份验证中间件,才能正常读取已登录用户的信息 app.UseAuthentication(); // 注册强制修改密码的自定义中间件 app.UseMiddleware<ForceChangePasswordMiddleware>(); app.UseAuthorization();
修改原有登录逻辑,登录时将「是否为默认密码」的标记存入Claims:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Login([FromForm] LoginViewModel vm, string returnUrl = null) { if (ModelState.IsValid) { var result = await repository.Login(vm); if (result.IsSuccess) { // 新增:给用户Claims添加是否为默认密码的自定义标记 var claims = result.Data.Claims.ToList(); claims.Add(new Claim("IsDefaultPassword", (vm.Password == Password.DefaultPassword).ToString())); var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, newPrincipal, new AuthenticationProperties { IssuedUtc = DateTime.UtcNow, ExpiresUtc = DateTime.UtcNow.AddDays(1), IsPersistent = false }); if (vm.Password != Password.DefaultPassword) { return RedirectToLocal(returnUrl); } else { return RedirectToAction(nameof(UserController.NewPassword)); } } else { ViewBag.ErrorMessage = result.ErrorMessage; } } return View(vm); }
最后修改密码成功后,更新用户Claims的标记,解除访问限制:
// 修改密码成功后的逻辑 var claims = User.Claims.Where(c => c.Type != "IsDefaultPassword").ToList(); claims.Add(new Claim("IsDefaultPassword", bool.FalseString)); var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)); // 重新登录更新Cookie中的Claims信息 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, newPrincipal); // 跳转至业务页面即可 return RedirectToAction("Index", "Home");
方案2:使用全局Action过滤器实现
如果你更习惯用过滤器的模式,也可以通过自定义全局Action过滤器实现相同效果,代码如下:
public class ForceChangePasswordFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // 排除匿名访问接口、修改密码本身的接口 if (context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any() || context.ActionDescriptor.DisplayName.Contains("NewPassword")) { return; } if (!context.HttpContext.User.Identity.IsAuthenticated) { return; } var isDefaultPasswordClaim = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "IsDefaultPassword"); if (isDefaultPasswordClaim != null && bool.TryParse(isDefaultPasswordClaim.Value, out var isDefault) && isDefault) { context.Result = new RedirectToActionResult("NewPassword", "User", null); } } public void OnActionExecuted(ActionExecutedContext context) { } }
在Program.cs中注册全局过滤器即可:
builder.Services.AddControllersWithViews(options => { options.Filters.Add<ForceChangePasswordFilter>(); });
注意事项
- 不要将明文密码存入Session或Claims,仅存储
是否为默认密码的布尔值即可,避免安全风险 - 必须正确配置白名单,否则会出现重定向循环问题
- 两种方案都是全局生效,后续新增控制器无需额外添加校验逻辑,维护成本远低于单个控制器重复写校验
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

