如何在.NET 6 WebAPI中支持dotnet user-jwt生成的令牌?
问题描述
在.NET 6 WebAPI项目中需要同时支持两种JWT令牌:
- 由
dotnet user-jwt生成的本地开发令牌 - 其他来源生成的正式环境令牌(该特性已在.NET 7中原生支持)
已尝试配置两个JWT Bearer认证方案(jwtAuth1和local),并设置授权策略同时启用这两个方案,但目前仅触发了两个方案的Challenge事件,无法完成local令牌的授权验证。
当前配置代码
认证服务配置
builder.Services.AddAuthentication(options => { //options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; //options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer("jwtAuth1", options => { options.Authority = authSettings.Schemes.Bearer.ValidIssuer; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = authSettings.Schemes.Bearer.ValidIssuer, ValidateLifetime = true, ValidateAudience = false }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { Log.Logger.Debug("message received"); if (string.IsNullOrEmpty(context.Request.Headers.Authorization)) { Log.Logger.Debug("Authorization header is empty"); } return Task.CompletedTask; }, OnAuthenticationFailed = context => { Log.Logger.Debug("Authentication Failed: {ExceptionMessage} {Token}", context.Exception.Message, context.Request.Headers.Authorization); return Task.CompletedTask; }, OnTokenValidated = context => { Log.Logger.Debug("token validated"); return Task.CompletedTask; }, OnForbidden = context => { Log.Logger.Debug("OnForbidden"); return Task.CompletedTask; }, OnChallenge = context => { Log.Logger.Debug("OnChallenge cognito"); return Task.CompletedTask; } }; }) .AddJwtBearer("local", options => { options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, //ValidIssuer = authSettings.Schemes.Bearer.ValidIssuer, ValidateLifetime = false, ValidateAudience = false, ValidateIssuerSigningKey = false, SignatureValidator = (token, parameters) => { var jwt = new JwtSecurityToken(token); return jwt; } }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { Log.Logger.Debug("message received local"); //context.Token = context.HttpContext.Request.Headers.Authorization; return Task.FromResult(0); }, OnTokenValidated = context => { Log.Logger.Debug("Token validated with local"); return Task.CompletedTask; }, OnAuthenticationFailed = context => { Log.Logger.Debug("Token failed with local"); return Task.CompletedTask; }, OnForbidden = context => { Log.Logger.Debug("Token forbidden with local"); return Task.CompletedTask; }, OnChallenge = context => { Log.Logger.Debug("Token challenge with local"); context.HandleResponse(); return Task.FromResult(0); } }; });
授权策略配置
builder.Services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes("jwtAuth1", "local") .RequireAuthenticatedUser() .Build(); });
解决方案
核心问题分析
local方案的OnChallenge事件中调用了context.HandleResponse(),会直接终止整个认证挑战流程,导致其他认证方案无法继续尝试。local方案未正确提取令牌,OnMessageReceived中未完成令牌的解析与赋值。- 完全关闭
ValidateIssuer存在安全风险,可针对dotnet user-jwt的默认Issuer做精准验证。
修改后的配置代码
1. 调整认证服务配置
builder.Services.AddAuthentication() .AddJwtBearer("jwtAuth1", options => { options.Authority = authSettings.Schemes.Bearer.ValidIssuer; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = authSettings.Schemes.Bearer.ValidIssuer, ValidateLifetime = true, ValidateAudience = false }; // 保留原有事件逻辑 options.Events = new JwtBearerEvents { OnMessageReceived = context => { Log.Logger.Debug("message received"); if (string.IsNullOrEmpty(context.Request.Headers.Authorization)) { Log.Logger.Debug("Authorization header is empty"); } return Task.CompletedTask; }, OnAuthenticationFailed = context => { Log.Logger.Debug("Authentication Failed: {ExceptionMessage} {Token}", context.Exception.Message, context.Request.Headers.Authorization); return Task.CompletedTask; }, OnTokenValidated = context => { Log.Logger.Debug("token validated"); return Task.CompletedTask; }, OnForbidden = context => { Log.Logger.Debug("OnForbidden"); return Task.CompletedTask; }, OnChallenge = context => { Log.Logger.Debug("OnChallenge cognito"); return Task.CompletedTask; } }; }) .AddJwtBearer("local", options => { options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { // 针对dotnet user-jwt默认issuer做验证,替代完全关闭 ValidateIssuer = true, ValidIssuer = "dotnet-user-jwts", // 开发环境可关闭过期验证,生产建议开启 ValidateLifetime = false, ValidateAudience = false, // 开启签名验证,提升安全性 ValidateIssuerSigningKey = true, // 通过dotnet user-jwts key命令获取本地签名密钥 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-local-jwt-secret")) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { Log.Logger.Debug("message received local"); // 正确提取Authorization头中的令牌(去掉Bearer前缀) var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { context.Token = authHeader["Bearer ".Length..].Trim(); } return Task.CompletedTask; }, OnTokenValidated = context => { Log.Logger.Debug("Token validated with local"); return Task.CompletedTask; }, OnAuthenticationFailed = context => { Log.Logger.Debug("Token failed with local: {Exception}", context.Exception.Message); return Task.CompletedTask; }, // 移除context.HandleResponse(),让认证流程继续尝试其他方案 OnChallenge = context => { Log.Logger.Debug("Token challenge with local"); return Task.CompletedTask; } }; });
2. 调整授权策略(可选:优先尝试本地令牌)
如果希望优先验证本地开发令牌,可将local放在认证方案列表的首位:
builder.Services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes("local", "jwtAuth1") .RequireAuthenticatedUser() .Build(); });
额外说明
- 本地签名密钥获取:执行
dotnet user-jwts key命令可查看dotnet user-jwt生成令牌的签名密钥,替换代码中的your-local-jwt-secret。 - 生产环境建议开启
ValidateLifetime和签名验证,避免无效或伪造令牌通过验证。
内容的提问来源于stack exchange,提问作者jsp
相关产品推荐
相关产品推荐

