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

OpenIddict v6生产环境授权流程报错:需指定授权类型

问题:OpenIddict v6 + .NET 9 BFF生产环境授权流程异常

基于OpenIddict v6(6.0.0)和.NET 9开发的BFF,触发授权流程时抛出以下异常:

"A grant type must be specified when triggering authentication demands from endpoints that are not managed by the OpenIddict client stack."

本地开发环境授权流程完全正常,但将授权服务器与BFF部署到Linux Ubuntu 24.04虚拟机的生产环境后,触发授权就会报错。已通过扩展方法完成OpenIddict客户端注册配置,且数据库中重定向URI与配置一致,怀疑问题可能与反向代理有关,寻求排查方向及解决建议。


完整异常栈信息

System.InvalidOperationException: A grant type must be specified when triggering authentication demands from endpoints that are not managed by the OpenIddict client stack. This error may also indicate that the redirection endpoint was not correctly enabled in the OpenIddict client options.
   at OpenIddict.Client.OpenIddictClientHandlers+ValidateAuthenticationDemand.HandleAsync(ProcessAuthenticationContext context)
   at OpenIddict.Client.OpenIddictClientDispatcher+<DispatchAsync>d__4<TContext>.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at OpenIddict.Client.OpenIddictClientDispatcher+<DispatchAsync>d__4<TContext>.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreHandler+<HandleAuthenticateAsync>d__4.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult()
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler<TOptions>+<AuthenticateAsync>d__54.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult()
   at Microsoft.AspNetCore.Authentication.AuthenticationService+<AuthenticateAsync>d__14.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult()
   at ResearchGuru.BackendForFrontend.Controllers.AccountController+<LoginCallback>d__12.MoveNext() in /root/actions-runner/_work/ResearchGuru.Backend/ResearchGuru.Backend/ResearchGuru.BackendForFrontend/Controllers/AccountController.cs:line 103
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor+<Execute>d__0.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at System.Runtime.CompilerServices.ValueTaskAwaiter<TResult>.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker+<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker+<<InvokeNextActionFilterAsync>g__Awaited|10_0>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker+<<InvokeNextResourceFilter>g__Awaited|25_0>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker+<<InvokeAsync>g__Logged|17_1>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker+<<InvokeAsync>g__Logged|17_1>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware+<Invoke>d__11.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task, ConfigureAwaitOptions options)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl+<<Invoke>g__Awaited|10_0>d.MoveNext()

OpenIddict客户端注册配置代码

public static void ConfigureOpenIddictClient(this IServiceCollection serviceCollection,
        IConfiguration configuration, bool isDevelopment)
    {
        var appConfiguration = configuration.GetSection(nameof(ResearchGuruAppConfiguration))
            .Get<ResearchGuruAppConfiguration>();

        serviceCollection.AddOpenIddict()
            .AddCore(config =>
            {
                config.UseEntityFrameworkCore(cfgg =>
                {
                    cfgg.UseDbContext<ResearchGuruDbContext>();
                    cfgg.ReplaceDefaultEntities<string>();
                });
            })
            .AddClient(config =>
            {
                config.AllowPasswordFlow();
                config.AllowAuthorizationCodeFlow();
                config.AllowRefreshTokenFlow();
                config.AllowClientCredentialsFlow();

                if (!isDevelopment)
                {
                    config.AddEncryptionCertificate(SecurityCertificateHelper.GetEncryptionCertificate())
                        .AddSigningCertificate(SecurityCertificateHelper.GetSigningCertificate())
                        .AddSigningKey(new SymmetricSecurityKey(
                            Encoding.UTF8.GetBytes(ConstantValues.EncryptionConstants
                                .TokenSigningKey)));
                }
                else
                {
                    config.AddDevelopmentEncryptionCertificate()
                        .AddDevelopmentSigningCertificate()
                        .AddSigningKey(new SymmetricSecurityKey(
                            Encoding.UTF8.GetBytes(ConstantValues.EncryptionConstants
                                .TokenSigningKey)));
                }

                config.UseAspNetCore()
                    .EnableStatusCodePagesIntegration()
                    .EnableRedirectionEndpointPassthrough()
                    .EnablePostLogoutRedirectionEndpointPassthrough();

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


                config.AddRegistration(new OpenIddictClientRegistration
                {
                    ClientId = isDevelopment
                        ? DevClientConstants.ResearchGuruBFFClientId
                        : ProdClientConstants.ResearchGuruBFFProdClientId,
                    ClientSecret = isDevelopment
                        ? DevClientConstants.ResearchGuruBFFClientSecret
                        : ProdClientConstants.ResearchGuruBFFProdClientSecret,
                    ProviderName = isDevelopment ? "Local" : null,
                    Issuer = new Uri($"{appConfiguration!.AuthorizationServerBaseUrl}"),
                    Scopes =
                    {
                        OpenIddictConstants.Scopes.Email,
                        OpenIddictConstants.Scopes.Profile,
                        OpenIddictConstants.Scopes.Roles,
                        OpenIddictConstants.Scopes.OfflineAccess,
                        OpenIddictConstants.Scopes.OpenId,
                    },
                    RedirectUri = new Uri($"{appConfiguration!.BaseUrl}/Account/LoginCallback"),
                    PostLogoutRedirectUri = new Uri($"{appConfiguration!.BaseUrl}/Account/LogoutCallback"),
                });
            });
    }

BFF触发授权流程的LoginRedirect端点代码

[HttpGet]
    public async Task<IActionResult> LoginRedirect()
    {
        var parameters = new Dictionary<string, object>
            (StringComparer.Ordinal)
            {
                { OpenIddictConstants.Parameters.ResponseType, "code" },
            };

        var items = new Dictionary<string, string>
            (StringComparer.Ordinal)
            {
                [OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = "Local"
            }!;

        var isDevelopment = Environment.GetEnvironmentVariable(SettingNames.AspNetCoreEnvironment)!.Equals(ConstantValues.Development);
        var properties = new AuthenticationProperties(isDevelopment ? items! : null, parameters!);

        return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
    }

解决建议

1. 修复生产环境ProviderName匹配问题

生产环境注册的OpenIddictClientRegistration将ProviderName设为null,但LoginRedirect仅在开发环境向AuthenticationProperties添加ProviderName为"Local"的items。生产环境下发起Challenge时未指定ProviderName,导致OpenIddict无法匹配到正确的客户端注册,进而无法识别授权类型。

  • 方案1:生产环境注册客户端时设置ProviderName = "Local"(与开发环境一致);
  • 方案2:在LoginRedirect的生产环境分支中,同样将ProviderName添加到AuthenticationProperties的items中。

2. 配置反向代理转发头

生产环境使用反向代理时,ASP.NET Core可能无法正确获取外部请求的Scheme/Host,导致OpenIddict验证重定向URI失败或无法识别端点归属。

  • 在Program.cs中添加转发头中间件:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
  • 确保反向代理(如Nginx)正确设置X-Forwarded-Proto和X-Forwarded-For请求头。

3. 明确指定授权类型参数

虽然代码中设置了ResponseType = "code",但可尝试直接添加GrantType参数,满足OpenIddict的验证要求:
在parameters字典中添加:

{ OpenIddictConstants.Parameters.GrantType, OpenIddictConstants.GrantTypes.AuthorizationCode }

4. 检查生产环境证书权限

生产环境通过SecurityCertificateHelper加载加密/签名证书,需确认:

  • 证书文件路径正确;
  • Linux下证书文件权限足够(确保应用进程有读取权限);
  • 证书未过期且格式正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 04:39:52