ASP.NET Core Web API多JWT Bearer令牌身份认证401问题排查
问题背景
我正在为ASP.NET Core Web API配置多个.NET Identity认证:
- 第一个Identity用于集团分支API用户,访问公共服务
- 每个分支单独配置Identity,用户认证后访问专属服务
- 用两个不同JWT令牌区分权限
已完成两个Identity的注入配置,但使用[Authorize(AuthenticationSchemes = "Bearer_ApiUser")]限制访问时,能正常获取主Identity令牌,但访问接口返回无详细提示的401错误。移除[Authorize]后接口正常运行。
备注:因分支用户字段/声明差异大、部分用户跨分支,必须保留独立Identity。
现有配置代码
公共Identity注入代码
public static class ApiIdentityServicesRegistration { public static IServiceCollection AddApiIdentityServices(this IServiceCollection services, IConfiguration configuration) { services.Configure<JwtSettings>(configuration.GetSection("ApiJwtSettings")); services.AddDbContext<ApiDbContext>(options => { options.UseSqlServer(configuration.GetConnectionString("API_IDENTITY")); }); services.AddIdentity<ApiApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApiDbContext>() .AddDefaultTokenProviders(); services.AddTransient<IApiUserRepository, UserService>(); services .AddAuthentication() .AddJwtBearer("Bearer_ApiUser", o => { o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, ValidIssuer = configuration["ApiJwtSettings:Issuer"], ValidAudience = configuration["ApiJwtSettings:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["ApiJwtSettings:Key"])) }; }); services.AddAuthorization(); services.ConfigureApplicationCookie(options => { options.Cookie.Name = "AajApiToken"; }); return services; } }
分支Identity注入代码
public static class AajIdentityServicesRegistration { public static IServiceCollection AddAajIdentityServices(this IServiceCollection services, IConfiguration configuration) { services.Configure<JwtSettings>(configuration.GetSection("AajJwtSettings")); services.AddDbContext<AajDbContext>(options => { options.UseSqlServer(configuration.GetConnectionString("AAJ_IDENTITY")); }); services.AddIdentityCore<AajApplicationUser>() .AddEntityFrameworkStores<AajDbContext>() .AddDefaultTokenProviders() .AddSignInManager<SignInManager<AajApplicationUser>>(); services.AddTransient<IAajUserRepository, AajUserService>(); services .AddAuthentication() .AddJwtBearer("Bearer_AajUser",o => { o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, ValidIssuer = configuration["AajJwtSettingsJwtSettings:Issuer"], ValidAudience = configuration["AajJwtSettings:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AajJwtSettings:Key"])) }; }); services.ConfigureApplicationCookie(options => { options.Cookie.Name = "AajUserToken"; }); return services; } }
问题排查步骤
1. 检查配置拼写错误
从分支Identity代码中发现:ValidIssuer = configuration["AajJwtSettingsJwtSettings:Issuer"]存在拼写错误,多写了一个JwtSettings,应改为AajJwtSettings:Issuer。同时需确认公共Identity的配置项名称与appsettings.json完全一致。
2. 启用JWT认证日志
在Program.cs中添加日志过滤配置,查看认证失败的详细原因:
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication", LogLevel.Debug);
同时在appsettings.json中设置对应日志级别:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Microsoft.AspNetCore.Authentication": "Debug" } } }
运行后查看日志,会输出JWT验证失败的具体原因(如签名无效、受众不匹配、颁发者错误等)。
3. 验证令牌内容
使用JWT解析工具解析获取到的令牌,检查:
iss字段是否与配置的ValidIssuer一致aud字段是否与配置的ValidAudience一致- 签名密钥是否与配置中的
ApiJwtSettings:Key一致 - 令牌是否过期
4. 确认认证中间件顺序
确保在Program.cs中,app.UseAuthentication()在app.UseAuthorization()之前调用,顺序错误会导致认证无效:
app.UseAuthentication(); app.UseAuthorization();
5. 检查认证方案匹配
确认[Authorize(AuthenticationSchemes = "Bearer_ApiUser")]中的方案名称与注册的JWT认证方案名称完全一致(注意大小写),也可显式设置默认认证方案(可选):
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "Bearer_ApiUser"; options.DefaultChallengeScheme = "Bearer_ApiUser"; }) .AddJwtBearer("Bearer_ApiUser", o => ...);
6. 检查令牌生成时的声明
确保生成JWT令牌时添加了Identity所需的必要声明(如NameIdentifier、Name等),示例代码:
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(jwtSettings.Key)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: jwtSettings.Issuer, audience: jwtSettings.Audience, claims: claims, expires: DateTime.Now.AddMinutes(jwtSettings.ExpiryMinutes), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token);
总结
优先排查配置拼写错误和令牌内容匹配问题,通过启用认证日志可快速定位401的具体原因,同时确保中间件顺序正确、令牌生成包含必要声明。
内容的提问来源于stack exchange,提问作者Charles M

