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

ASP.NET Core 5 Razor Pages强制改密码及URL拦截方案问询

ASP.NET Core 5 Razor Pages 强制改密码实现方案

1. 配置密码有效期

先在appsettings.json中添加密码过期天数的配置:

{
  "AppSettings": {
    "PasswordExpiryDays": 90
  }
}

创建配置映射类AppSettings.cs:

public class AppSettings
{
    public int PasswordExpiryDays { get; set; }
}

在Startup.cs的ConfigureServices方法中注册配置:

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

2. 实现全局页面过滤器拦截请求

创建ForcePasswordChangeFilter.cs,作为全局页面过滤器,检查用户是否需要强制改密码:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Threading.Tasks;

public class ForcePasswordChangeFilter : IAsyncPageFilter
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly AppSettings _appSettings;

    public ForcePasswordChangeFilter(UserManager<ApplicationUser> userManager, IOptions<AppSettings> appSettings)
    {
        _userManager = userManager;
        _appSettings = appSettings.Value;
    }

    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
    {
        // 排除改密码和登录页面,防止循环跳转
        var requestPath = context.HttpContext.Request.Path.Value;
        if (requestPath.Contains("/Account/ChangePassword") || requestPath.Contains("/Account/Login"))
        {
            await next();
            return;
        }

        // 获取当前登录用户
        var user = await _userManager.GetUserAsync(context.HttpContext.User);
        if (user == null)
        {
            await next();
            return;
        }

        // 判断是否需要强制改密码
        bool requiresPasswordChange = false;
        if (user.LastPasswordChange == null)
        {
            // 管理员创建的用户首次登录
            requiresPasswordChange = true;
        }
        else
        {
            // 检查密码是否过期(用UTC时间避免时区问题)
            var expiryDate = user.LastPasswordChange.Value.AddDays(_appSettings.PasswordExpiryDays);
            if (DateTime.UtcNow > expiryDate)
            {
                requiresPasswordChange = true;
            }
        }

        if (requiresPasswordChange)
        {
            // 跳转到改密码页面,携带原路径以便改完后跳转
            context.Result = new RedirectToPageResult("/Account/ChangePassword", new { returnUrl = requestPath });
            return;
        }

        await next();
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        return Task.CompletedTask;
    }
}

在Startup.cs中注册这个过滤器为全局服务:

services.AddScoped<ForcePasswordChangeFilter>();

services.AddRazorPages(options =>
{
    options.Filters.AddService<ForcePasswordChangeFilter>();
});

3. 改密码页面逻辑实现

在/Account/ChangePassword.cshtml.cs中,完成密码修改并更新LastPasswordChange字段:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;

public class ChangePasswordModel : PageModel
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;

    public ChangePasswordModel(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    [BindProperty]
    public InputModel Input { get; set; }

    public class InputModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "当前密码")]
        public string OldPassword { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "{0} 长度至少为 {2} 个字符。", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "新密码")]
        public string NewPassword { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "确认新密码")]
        [Compare("NewPassword", ErrorMessage = "新密码与确认密码不匹配。")]
        public string ConfirmPassword { get; set; }
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl ??= Url.Content("~/");

        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _userManager.GetUserAsync(User);
        if (user == null)
        {
            return NotFound($"无法加载用户 ID '{_userManager.GetUserId(User)}'。");
        }

        // 修改密码
        var changeResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
        if (!changeResult.Succeeded)
        {
            foreach (var error in changeResult.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
            return Page();
        }

        // 更新密码修改时间为当前UTC时间
        user.LastPasswordChange = DateTime.UtcNow;
        await _userManager.UpdateAsync(user);

        // 刷新登录状态,确保用户信息同步
        await _signInManager.RefreshSignInAsync(user);

        return LocalRedirect(returnUrl);
    }
}

4. 管理员创建用户的初始化逻辑

管理员创建用户时,将LastPasswordChange设为null,标记为首次登录需要改密码:

// 示例:管理员创建用户代码
var newUser = new ApplicationUser
{
    UserName = input.Email,
    Email = input.Email,
    LastPasswordChange = null // 首次登录需要改密码
};

var createResult = await _userManager.CreateAsync(newUser, input.Password);
if (createResult.Succeeded)
{
    // 后续逻辑...
}

关键注意事项

  • 避免循环跳转:必须排除改密码和登录页面,否则会陷入无限重定向
  • 时区处理:统一使用UTC时间比较,避免不同时区导致的判断错误
  • 登录状态刷新:改完密码后调用RefreshSignInAsync,确保用户缓存信息更新
  • 提示信息:可以在改密码页面添加文本提示,告知用户改密码的原因(首次登录/密码过期)

内容的提问来源于stack exchange,提问作者Mehedi Hasan Kajol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 21:45:38