JWT认证中ClaimsPrincipal多身份及IsAuthenticated=false问题排查
解决方案
一、解决多身份与IsAuthenticated=false问题
核心原因是IdentityCore默认认证方案(Cookie)与JWT认证方案冲突,导致两个认证中间件同时处理请求,一个验证成功(JWT)、一个验证失败(未配置Cookie),从而生成多个身份。
1. 统一认证方案配置
修改Program.cs中的认证服务配置,将默认认证方案设为JWT,避免Identity的Cookie认证干扰:
// 配置IdentityCore(仅用于用户管理,不做默认认证) builder.Services.AddIdentityCore<ApplicationUser>(options => { // 按需配置密码、用户验证规则 }) .AddEntityFrameworkStores<ApplicationDbContext>(); // 配置JWT为默认认证方案 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); builder.Services.AddAuthorization();
2. 修正Logout端点认证
JWT场景下,Logout无需服务端销毁令牌(客户端删除本地令牌即可)。若需服务端注销(如令牌黑名单),需明确指定JWT认证方案保护端点:
[HttpPost("logout")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public async Task<IActionResult> Logout() { // 可选:将令牌加入黑名单(如存入Redis) var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last(); await _redisCache.SetStringAsync($"Blacklist:{token}", "revoked", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); return Ok("Logout successful"); }
二、解决自定义Claims不生效问题
自定义Claims需同时实现ClaimsPrincipalFactory扩展和JWT令牌生成时的Claims注入。
1. 实现自定义UserClaimsPrincipalFactory
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser> { public CustomUserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor) { } protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user) { var identity = await base.GenerateClaimsAsync(user); // 添加自定义Claims identity.AddClaim(new Claim("UserFullName", user.FullName)); identity.AddClaim(new Claim(ClaimTypes.Role, user.Role)); return identity; } }
2. 注册自定义Factory并生成含Claims的JWT
在Program.cs中替换默认Factory:
builder.Services.AddIdentityCore<ApplicationUser>(options => { // 配置项 }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>(); // 注册自定义Factory
登录时从UserManager获取包含自定义Claims的主体,注入到JWT中:
[HttpPost("login")] public async Task<IActionResult> Login(LoginModel model) { var user = await _userManager.FindByEmailAsync(model.Email); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { // 获取含自定义Claims的ClaimsPrincipal var claimsPrincipal = await _userManager.CreateUserPrincipalAsync(user); var claims = claimsPrincipal.Claims.ToList(); var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddHours(1), Issuer = _configuration["Jwt:Issuer"], Audience = _configuration["Jwt:Audience"], SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return Ok(new { Token = tokenHandler.WriteToken(token) }); } return Unauthorized(); }
三、关键检查项
- 确保中间件顺序正确:
app.UseAuthentication()必须在app.UseAuthorization()之前,且仅调用一次。 - 避免同时启用多个默认认证方案,如需支持多认证方式,在
[Authorize]属性中明确指定方案(如[Authorize(AuthenticationSchemes = "JwtBearer,Cookies")])。
内容的提问来源于stack exchange,提问作者Nasser X
相关产品推荐
相关产品推荐

