如何为不同用户动态配置ASP.NET Identity的MaxFailedAccessAttempts
动态配置ASP.NET Identity用户专属的MaxFailedAccessAttempts
要实现不同用户拥有不同的登录失败锁定阈值,且值存储在缓存中,核心是重写Identity框架中获取最大失败尝试次数的逻辑,让其优先从缓存读取用户专属配置,再 fallback 到全局默认值。以下是具体实现步骤:
1. 自定义UserLockoutStore,重写阈值获取方法
Identity默认通过UserLockoutStore的GetMaxFailedAccessAttemptsAsync方法获取全局配置的阈值,我们需要继承默认的UserStore并重写该方法,改为从缓存读取用户专属值:
public class CustomUserLockoutStore : UserStore<ApplicationUser, Role, ApplicationDbContext, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, IdentityUserToken<string>, IdentityRoleClaim<string>> { private readonly IDistributedCache _cache; private readonly IOptions<IdentityOptions> _identityOptions; public CustomUserLockoutStore(ApplicationDbContext context, IDistributedCache cache, IOptions<IdentityOptions> identityOptions) : base(context) { _cache = cache; _identityOptions = identityOptions; } public override async Task<int> GetMaxFailedAccessAttemptsAsync(ApplicationUser user, CancellationToken cancellationToken = default) { // 构造缓存Key,格式示例:User:Lockout:用户ID var cacheKey = $"User:Lockout:{user.Id}"; var cachedBytes = await _cache.GetAsync(cacheKey, cancellationToken); if (cachedBytes != null && int.TryParse(Encoding.UTF8.GetString(cachedBytes), out int userAttempts)) { return userAttempts; } // 缓存无值时,返回全局默认配置 return _identityOptions.Value.Lockout.MaxFailedAccessAttempts; } }
2. 替换默认的UserStore到DI容器
在Program.cs(或Startup.cs)中注册Identity时,替换默认的用户存储为我们自定义的CustomUserLockoutStore:
// 保留原Identity配置,全局默认值作为兜底 services.AddIdentity<ApplicationUser, Role>(config => { config.Lockout.MaxFailedAccessAttempts = 3; // 可设为经理用户默认值,或通用兜底值 config.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(24); }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddUserStore<CustomUserLockoutStore>(); // 替换为自定义存储
3. 登录时加载用户专属配置到缓存
在登录逻辑中,先从数据源(比如数据库用户表的自定义字段)读取该用户的阈值,存入缓存后再执行登录验证:
public async Task<IActionResult> Login(LoginViewModel model) { if (!ModelState.IsValid) return View(model); var user = await _userManager.FindByNameAsync(model.UserName); if (user == null) { ModelState.AddModelError("", "无效的登录信息"); return View(model); } // 假设用户表有自定义字段MaxFailedAccessAttempts,从数据库读取后存入缓存 var userAttempts = user.MaxFailedAccessAttempts; // 示例:经理用户3次,普通用户5次 var cacheKey = $"User:Lockout:{user.Id}"; await _cache.SetStringAsync(cacheKey, userAttempts.ToString(), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24) // 设置缓存有效期 }); // 执行登录验证,此时Identity会调用自定义Store的方法获取阈值 var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: true); if (result.Succeeded) { return RedirectToAction("Index", "Home"); } else if (result.IsLockedOut) { ModelState.AddModelError("", "账号已锁定,请24小时后重试"); return View(model); } else { ModelState.AddModelError("", "登录失败,请检查用户名或密码"); return View(model); } }
补充说明
- 若用户的阈值需要更新,只需修改缓存对应Key的值,下次验证时会自动生效;
- 缓存过期后会自动 fallback 到全局默认值,也可以在用户资料修改逻辑中同步更新缓存;
- 该方案覆盖了Identity所有涉及锁定阈值的场景(如密码错误计数、锁定判断),无需修改多处逻辑。
内容的提问来源于stack exchange,提问作者jkamz
相关产品推荐
相关产品推荐

