IdentityServer4添加额外Claim后记住我失效 Cookie变为会话Cookie
IdentityServer4 自定义Claim导致记住我功能失效问题解决方案
问题诱因
- 重复登录操作覆盖原有Cookie:代码中先调用
_signInManager.PasswordSignInAsync已经完成了身份校验并签发了带持久化属性的.AspNetCore.Identity.ApplicationCookie,后续主动调用HttpContext.SignInAsync生成的新Cookie直接覆盖了原有配置。 - 自定义Claim触发Cookie大小限制:新增的自定义Claim增大了Cookie总容量,当总大小超过浏览器单Cookie 4KB上限时,Cookie的
Expires/Max-Age等持久化属性会被截断丢失,浏览器会默认将其识别为会话Cookie。移除自定义Claim后Cookie体积回落至限制以内,持久化属性即可正常保留。 - 安全戳验证刷新Cookie丢失配置:两次登录生成的Claims集合不一致,ASP.NET Core Identity内置的安全戳验证逻辑会检测到身份信息变更,自动重新签发Cookie,重签时未继承原有
IsPersistent配置,默认生成会话Cookie。
解决方案
方案1:使用自定义Claims工厂(推荐)
将自定义Claim的生成逻辑接入Identity原生流程,从根源避免重复登录、Claims不一致的问题:
- 实现自定义用户Claims工厂,将需要的自定义Claim加入身份票据:
public class CustomUserClaimsFactory : UserClaimsPrincipalFactory<你的用户类> { public CustomUserClaimsFactory(UserManager<你的用户类> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor) { } protected override async Task<ClaimsIdentity> GenerateClaimsAsync(你的用户类 user) { var identity = await base.GenerateClaimsAsync(user); identity.AddClaim(new Claim("tenantId", user.TenantId.HasValue ? user.TenantId.Value.ToString() : "0")); identity.AddClaim(new Claim(ClaimTypes.Name, user.Email)); return identity; } }
- 在服务注册时替换原生Claims工厂:
// .NET 6+ Program.cs 写法 builder.Services.AddDefaultIdentity<你的用户类>() .AddClaimsPrincipalFactory<CustomUserClaimsFactory>();
- 清理登录代码中的重复登录逻辑,仅保留
_signInManager.PasswordSignInAsync调用即可,自定义Claim会自动写入Cookie,记住我配置会正常生效。
方案2:调整手动登录逻辑
如果需要完全自主控制登录流程,可移除原生PasswordSignInAsync调用,统一走手动登录逻辑,同时适配大Cookie场景:
- 配置Identity Cookie开启分片,解决大Cookie截断问题:
builder.Services.ConfigureApplicationCookie(options => { // 开启自动分片,超过阈值时拆分为多个Cookie避免截断 options.CookieManager = new ChunkingCookieManager(); options.Cookie.IsEssential = true; options.SlidingExpiration = true; });
- 调整登录代码,先手动校验账号密码,再统一调用一次登录接口:
public async Task<IActionResult> LoginLocal(LoginInputModel model, string button) { var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (ModelState.IsValid) { var user = await _userManager.FindByNameAsync(model.Username); // 手动校验密码,移除原有PasswordSignInAsync调用 var passwordValid = await _userManager.CheckPasswordAsync(user, model.Password); if (passwordValid) { _logger.LogInformation(1, "User logged in."); await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)); var isuser = new IdentityServerUser(user.Id) { DisplayName = user.UserName, AdditionalClaims = new List<Claim> { new Claim("tenantId", user.TenantId.HasValue ? user.TenantId.Value.ToString() : "0"), new Claim(ClaimTypes.Name, user.Email) } }; var props = new AuthenticationProperties { IsPersistent = model.RememberLogin, ExpiresUtc = model.RememberLogin ? DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) : null }; // 显式指定Identity认证Scheme,避免Scheme不匹配问题 await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, isuser.CreatePrincipal(), props); // 原有登录成功跳转逻辑 if (context != null) { return Redirect(model.ReturnUrl); } return Redirect("~/"); } } var vm = await BuildLoginViewModelAsync(model); return View(vm); }
内容的提问来源于stack exchange,提问作者zaid mousa
相关产品推荐
相关产品推荐

