Asp.Net Core Identity认证Cookie超出4096字符问题求助
兄弟,这个问题我之前做SPA项目的时候也踩过坑!核心原因就是默认情况下Identity会把用户对象的关联数据和所有Claims都序列化进认证Cookie,而浏览器对单个Cookie的大小限制是4KB(4096字符),一旦超了就会直接忽略这个Cookie,导致认证失效。
具体到你的代码来看,问题出在这几个地方:
- 你的
User类里有AccessList(集合类型)和Person关联对象,Identity生成认证票据时,可能会把这些复杂数据自动转化为Claims或者直接序列化进去,瞬间让Cookie体积暴增 SignInManager.PasswordSignInAsync默认会加载用户的关联数据(比如你的AccessList和Person),这些数据都会被打包进Cookie里
给你几个实用的解决方案,按从根本到治标的顺序来:
1. 自定义Claims,只存必要数据
别让Identity瞎塞数据,手动控制哪些信息放进Cookie。你可以实现IUserClaimsPrincipalFactory<User>来定制Claims生成逻辑:
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<User, IdentityRole<int>> { public CustomUserClaimsPrincipalFactory( UserManager<User> userManager, RoleManager<IdentityRole<int>> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor) { } protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user) { var identity = await base.GenerateClaimsAsync(user); // 只保留你真正需要的Claims,比如用户ID、用户名就行 identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName)); // 把默认可能加进去的多余Claims删掉,比如关联的Person、AccessList相关的 var extraClaims = identity.Claims.Where(c => c.Type.Contains("Person") || c.Type.Contains("AccessList") || c.Type.StartsWith("urn:identity:")); foreach (var claim in extraClaims.ToList()) { identity.RemoveClaim(claim); } return identity; } }
然后在Startup.cs里注册这个自定义工厂:
services.AddScoped<IUserClaimsPrincipalFactory<User>, CustomUserClaimsPrincipalFactory>();
2. 禁止自动加载用户关联数据
在查询用户的时候,别让EF Core自动加载AccessList和Person这些关联对象。修改你的登录代码:
// 只查基本用户信息,不加载关联数据 var user = await userManager.Users .AsNoTracking() // 如果你不需要Person和AccessList,就不要加Include // .Include(u => u.Person) // .Include(u => u.AccessList) .FirstOrDefaultAsync(u => u.UserName == loginModel.Name);
如果你的DbContext开了懒加载,最好也关掉,避免不经意间加载大量关联数据。
3. 让Cookie自动拆分(治标)
如果确实需要存较多数据,可以配置Identity使用ChunkingCookieManager自动把大Cookie拆分成多个小Cookie:
services.ConfigureApplicationCookie(options => { options.CookieManager = new ChunkingCookieManager(); // 自动拆分Cookie options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; });
不过这个只是临时解决,还是优先精简Claims更靠谱。
4. 换成JWT认证(SPA更推荐)
既然是SPA应用,其实用JWT比Cookie更合适!JWT存在前端的localStorage/sessionStorage里,没有4KB限制,而且更符合前后端分离的架构。
在Startup.cs里配置JWT认证:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = Configuration["Jwt:Issuer"], // 从配置文件读取 ValidAudience = Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) }; });
然后登录接口改成生成JWT返回给前端:
public async Task<IActionResult> Login([FromBody]LoginModel loginModel) { var user = await userManager.FindByNameAsync(loginModel.Name); if (user != null && await userManager.CheckPasswordAsync(user, loginModel.Password)) { // 生成JWT var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: Configuration["Jwt:Issuer"], audience: Configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddDays(7), signingCredentials: creds); return Json(new { token = new JwtSecurityTokenHandler().WriteToken(token) }); } return Json(-1); }
这样前端拿到JWT后,每次请求都在请求头里带上Authorization: Bearer {token},就不用依赖Cookie了。
内容的提问来源于stack exchange,提问作者thesdev

