You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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();
        });

解决方案

核心问题分析

  1. local方案的OnChallenge事件中调用了context.HandleResponse(),会直接终止整个认证挑战流程,导致其他认证方案无法继续尝试。
  2. local方案未正确提取令牌,OnMessageReceived中未完成令牌的解析与赋值。
  3. 完全关闭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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 16:55:16