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
相关产品推荐
相关产品推荐

