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

如何在OpenIddict客户端中动态设置认证服务器URL

解决方案:动态适配OpenIddict客户端的认证服务器Issuer URI

针对你遇到的内网/公网环境下客户端无法动态切换认证服务器地址的问题,以下是几种可行的实现方案:


方案1:自定义注册提供者动态生成Issuer

通过替换OpenIddict默认的注册提供者,在每次请求时根据客户端的访问地址动态选择对应的认证服务器Issuer:

// 先确保注册HttpContextAccessor(ASP.NET Core默认已注册,手动DI需添加)
services.AddHttpContextAccessor();

services.AddOpenIddict()
    .AddClient(clientOptions =>
    {
        // 保留你原有的客户端配置...
        clientOptions
            .AllowAuthorizationCodeFlow()
            .AllowRefreshTokenFlow()
            .DisableTokenStorage();

        clientOptions.AddSigningKey(MySecretKey);
        clientOptions.AddEncryptionKey(MyEncryptionkey);

        clientOptions.UseAspNetCore()
            .EnableStatusCodePagesIntegration()
            .EnableRedirectionEndpointPassthrough()
            .EnablePostLogoutRedirectionEndpointPassthrough()
            .DisableTransportSecurityRequirement();

        clientOptions.UseSystemNetHttp()
            .SetProductInformation(typeof(Program).Assembly);

        // 添加基础注册信息(仅作为模板,Issuer会被动态替换)
        clientOptions.AddRegistration(new OpenIddictClientRegistration
        {   
            ClientId = "MyWebClientId",
            ClientSecret = "MyClientSercret",
            Scopes = { Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess},
            RedirectUri = new Uri("callback/login/local", UriKind.Relative),
            PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative)
        });

        // 替换默认注册提供者
        clientOptions.ReplaceRegistrationProvider<DynamicRegistrationProvider>();
    });

// 自定义动态注册提供者
public class DynamicRegistrationProvider : IOpenIddictClientRegistrationProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly OpenIddictClientRegistration _baseRegistration;

    public DynamicRegistrationProvider(IHttpContextAccessor httpContextAccessor, IOptionsMonitor<OpenIddictClientOptions> options)
    {
        _httpContextAccessor = httpContextAccessor;
        _baseRegistration = options.CurrentValue.Registrations.First();
    }

    public ValueTask<OpenIddictClientRegistration> GetRegistrationAsync(CancellationToken cancellationToken)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext is null)
        {
            return ValueTask.FromResult(_baseRegistration);
        }

        // 根据客户端访问地址判断内网/公网
        var requestHost = httpContext.Request.Host.Host;
        Uri issuerUri = requestHost.StartsWith("192.168.") || requestHost == "localhost"
            ? new Uri("http://192.168.1.101", UriKind.Absolute)
            : new Uri("https://194.183.157.20", UriKind.Absolute);

        // 复制基础配置并替换Issuer
        var dynamicRegistration = new OpenIddictClientRegistration(_baseRegistration)
        {
            Issuer = issuerUri
        };

        return ValueTask.FromResult(dynamicRegistration);
    }
}

方案2:拦截请求事件动态修改Issuer

通过监听OpenIddict客户端的请求事件,在发送请求前动态替换Issuer地址:

services.AddOpenIddict()
    .AddClient(clientOptions =>
    {
        // 保留你原有的客户端配置...
        clientOptions
            .AllowAuthorizationCodeFlow()
            .AllowRefreshTokenFlow()
            .DisableTokenStorage();

        clientOptions.AddSigningKey(MySecretKey);
        clientOptions.AddEncryptionKey(MyEncryptionkey);

        clientOptions.UseAspNetCore()
            .EnableStatusCodePagesIntegration()
            .EnableRedirectionEndpointPassthrough()
            .EnablePostLogoutRedirectionEndpointPassthrough()
            .DisableTransportSecurityRequirement();

        clientOptions.UseSystemNetHttp()
            .SetProductInformation(typeof(Program).Assembly);

        clientOptions.AddRegistration(new OpenIddictClientRegistration
        {   
            // 暂时设置一个默认Issuer,后续会被动态替换
            Issuer = new Uri("http://192.168.1.101", UriKind.Absolute),
            ClientId = "MyWebClientId",
            ClientSecret = "MyClientSercret",
            Scopes = { Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess},
            RedirectUri = new Uri("callback/login/local", UriKind.Relative),
            PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative)
        });

        // 拦截授权请求事件,动态修改Issuer
        clientOptions.AddEventHandler<OpenIddictClientEvents.ApplyAuthorizationRequestContext>(builder =>
        {
            builder.UseInlineHandler(context =>
            {
                UpdateIssuerBasedOnRequest(context.HttpContext, context.Registration);
                return default;
            });
        });

        // 拦截令牌请求事件
        clientOptions.AddEventHandler<OpenIddictClientEvents.ApplyTokenRequestContext>(builder =>
        {
            builder.UseInlineHandler(context =>
            {
                UpdateIssuerBasedOnRequest(context.HttpContext, context.Registration);
                return default;
            });
        });

        // 拦截登出请求事件
        clientOptions.AddEventHandler<OpenIddictClientEvents.ApplyLogoutRequestContext>(builder =>
        {
            builder.UseInlineHandler(context =>
            {
                UpdateIssuerBasedOnRequest(context.HttpContext, context.Registration);
                return default;
            });
        });
    });

// 提取通用的Issuer更新逻辑
private static void UpdateIssuerBasedOnRequest(HttpContext httpContext, OpenIddictClientRegistration registration)
{
    if (httpContext is null) return;

    var requestHost = httpContext.Request.Host.Host;
    registration.Issuer = requestHost.StartsWith("192.168.") || requestHost == "localhost"
        ? new Uri("http://192.168.1.101", UriKind.Absolute)
        : new Uri("https://194.183.157.20", UriKind.Absolute);
}

服务器端配套配置

确保认证服务器能正确处理多Issuer场景,在现有配置基础上添加动态Issuer支持:

services.AddOpenIddict()
    .AddCore(coreOptions =>
    {
        coreOptions.UseEntityFrameworkCore().UseDbContext<MyDbContext>();
        coreOptions.UseQuartz();
        coreOptions.ReplaceApplicationManager<CustomApplicationManager>();
    })
    .AddServer(serverOptions =>
    {
        serverOptions.Configure(options =>
        {
            options.TokenValidationParameters.ValidIssuers =
            [
                "http://192.168.1.101",
                "https://194.183.157.20",
            ];
            // 启用动态Issuer,让服务器根据当前请求地址生成正确的Issuer值
            options.UseDynamicIssuer();
        });

        // 保留你原有的服务器配置...
        serverOptions.SetAuthorizationEndpointUris("connect/authorize")
            .SetLogoutEndpointUris("connect/logout")
            .SetTokenEndpointUris("connect/token")
            .SetUserinfoEndpointUris("connect/userinfo")
            .SetRevocationEndpointUris("connect/revoke")
            .SetIntrospectionEndpointUris("connect/Introspect")
            .SetVerificationEndpointUris("connect/verify")
            .RequireProofKeyForCodeExchange();

        // ...其余配置不变
    })
    .AddValidation(validationOptions =>
    {
        validationOptions.UseLocalServer();
        validationOptions.UseAspNetCore();
        validationOptions.UseDataProtection();
    });

关键注意事项

  1. 你已使用相对路径配置RedirectUri和PostLogoutRedirectUri,这是正确的,确保客户端跳转地址能自动适配当前访问地址。
  2. 测试时需覆盖内网、公网两种场景,验证授权码流程、令牌刷新流程是否正常。
  3. 若使用方案1,需确保IHttpContextAccessor已在DI容器中注册。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 08:54:51