You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 19:06:04