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

如何在ASP.NET Core中间件中动态更新JWT Bearer配置?

ASP.NET Core JWT Bearer 运行时动态更新Backchannel解决方案

问题背景

开发基于ASP.NET Core的JWT Bearer认证应用时,需要在不重启应用的前提下,动态更新JWT Bearer的Backchannel(用于JWT密钥获取、令牌验证等操作的HttpClient)。具体场景是应用启动后从Azure Key Vault下载代理密码,配置到Backchannel的代理中。

当前遇到的问题:

  • 直接在AddJwtBearer中使用services.BuildServiceProvider()获取IHttpClientFactory可以实现功能,但这是ASP.NET Core中的反模式,会导致服务容器重复构建、依赖注入生命周期混乱。
  • 尝试在JwtBearerEvents.OnMessageReceived事件中修改options.Backchannel,但修改无法被中间件识别,不生效。

反模式代码示例:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidIssuer = "YourIssuer",
                ValidAudience = "YourAudience",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecretKey"))
            };

            // 反模式:提前构建服务容器
            var serviceProvider = services.BuildServiceProvider();
            var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
            options.Backchannel = httpClientFactory.Create("Proxy");
        });

无效的事件修改代码:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var httpClientFactory = context.HttpContext.RequestServices.GetRequiredService<IHttpClientFactory>();
                    options.Backchannel = httpClientFactory.CreateClient("Proxy");
                    return Task.CompletedTask;
                }
            };
        });

解决思路

1. 使用PostConfigureOptions延迟配置(避免反模式)

ASP.NET Core提供PostConfigureOptions机制,允许在所有服务配置完成后,再对选项进行最终配置。此时可以安全地从服务容器中获取依赖,无需提前构建服务容器。

代码示例:

// 先注册JWT认证基础配置
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidIssuer = "YourIssuer",
                ValidAudience = "YourAudience",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("YourSecretKey"))
            };
        });

// 延迟配置Backchannel,此时服务容器已就绪
services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, (options, serviceProvider) =>
{
    var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
    options.Backchannel = httpClientFactory.CreateClient("Proxy");
});

2. 运行时动态更新Backchannel(支持代理密码变化)

如果需要在应用运行时动态更新Backchannel的代理配置(比如Azure Key Vault中的密码更新),可以结合IOptionsMonitor监听配置变化,通过两种方式实现:

方式A:通过HttpClient的DelegatingHandler动态应用最新配置

定义代理配置类,绑定Azure Key Vault中的配置(需开启配置动态刷新):

public class ProxySettings
{
    public string ProxyPassword { get; set; }
    public string ProxyUrl { get; set; }
    public int ProxyPort { get; set; }
    public string ProxyUsername { get; set; }
}

配置HttpClientFactory,使用DelegatingHandler在每次请求时获取最新代理配置:

// 绑定ProxySettings到配置(Azure Key Vault配置提供器需提前配置)
services.AddOptions<ProxySettings>()
        .BindConfiguration("ProxySettings")
        .ValidateDataAnnotations();

// 注册带动态代理的HttpClient
services.AddHttpClient("Proxy")
        .AddHttpMessageHandler(sp =>
        {
            var proxySettingsMonitor = sp.GetRequiredService<IOptionsMonitor<ProxySettings>>();
            return new DynamicProxyHandler(proxySettingsMonitor);
        });

// 自定义DelegatingHandler,动态设置代理
public class DynamicProxyHandler : DelegatingHandler
{
    private readonly IOptionsMonitor<ProxySettings> _proxySettingsMonitor;

    public DynamicProxyHandler(IOptionsMonitor<ProxySettings> proxySettingsMonitor)
    {
        _proxySettingsMonitor = proxySettingsMonitor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 每次请求前获取最新的代理配置
        var settings = _proxySettingsMonitor.CurrentValue;
        var proxy = new WebProxy(new Uri($"{settings.ProxyUrl}:{settings.ProxyPort}"))
        {
            Credentials = new NetworkCredential(settings.ProxyUsername, settings.ProxyPassword)
        };
        
        request.Proxy = proxy;
        return await base.SendAsync(request, cancellationToken);
    }
}

最后通过PostConfigureOptions将该HttpClient设置为JWT的Backchannel:

services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, (options, sp) =>
{
    var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
    options.Backchannel = httpClientFactory.CreateClient("Proxy");
});

方式B:通过IOptionsMonitor直接更新JwtBearerOptions

当代理配置变化时,通过IOptionsMonitor更新JWT选项的Backchannel:

// 注册配置变更监听服务
services.AddHostedService<ProxySettingsUpdateService>();

public class ProxySettingsUpdateService : BackgroundService
{
    private readonly IOptionsMonitor<ProxySettings> _proxySettingsMonitor;
    private readonly IOptionsMonitor<JwtBearerOptions> _jwtOptionsMonitor;
    private readonly IHttpClientFactory _httpClientFactory;

    public ProxySettingsUpdateService(IOptionsMonitor<ProxySettings> proxySettingsMonitor, 
                                      IOptionsMonitor<JwtBearerOptions> jwtOptionsMonitor,
                                      IHttpClientFactory httpClientFactory)
    {
        _proxySettingsMonitor = proxySettingsMonitor;
        _jwtOptionsMonitor = jwtOptionsMonitor;
        _httpClientFactory = httpClientFactory;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 监听ProxySettings变化
        _proxySettingsMonitor.OnChange(settings =>
        {
            // 更新JWT的Backchannel
            _jwtOptionsMonitor.Update(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.Backchannel = _httpClientFactory.CreateClient("Proxy");
            });
        });

        return Task.CompletedTask;
    }
}

为什么之前的事件修改不生效?

JwtBearerMiddleware内部使用IOptionsMonitor<JwtBearerOptions>来获取配置,但在OnMessageReceived事件中直接修改的options对象是启动时的快照副本,并非中间件实时使用的配置实例。正确的做法是通过IOptionsMonitor的更新机制来修改配置,或者让Backchannel的HttpClient自身处理动态配置。

内容的提问来源于stack exchange,提问作者scattered

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:47:06