带有 ClientCredentials 流的 OpenIdDict 降级模式

问题描述

我正在关注这个关于允许 OpenIDDict 包装替代身份验证提供程序但从 OpenIDDict 本身返回 JWT 令牌的博客

https://kevinchalet.com/2020/02/18/creating-an-openid-connect-server-proxy-with-openiddict-3-0-s-degraded-mode/

这实际上是关于拦截授权代码流而不是客户端凭据流,但它提供了一个很好的起点。

不幸的是,它指出“我们不需要覆盖 HandletokenRequestContext”,这适用于博客,但不适用于(据我所知)我的用例。

我想我需要实现一个自定义的 HandletokenRequestContext,但是当我这样做时,代码运行,没有错误,但 HTTP 响应为空。没有生成令牌。

我应该如何正确拦截客户端凭据流,以便我可以调用一个提供程序来验证凭据、获取结果并将其包含在我需要添加到 JWT 的自定义声明中?

代码如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DbContext>(options =>
            {
                // Configure the context to use an in-memory store - probably not needed?
                options.UseInMemoryDatabase(nameof(DbContext));

                // Register the entity sets needed by OpenIddict.
                options.USEOpenIddict();
            });

            services.AddOpenIddict()
                .AddCore(options =>
                {
                    options.UseEntityFrameworkCore()
                                   .UseDbContext<DbContext>();
                })
                .AddServer(options =>
                {
                    options.SetTokenEndpointUris("/connect/token");
                    options
                        //.AllowRefreshTokenFlow()
                        .AllowClientCredentialsFlow();

                    // Register the signing and encryption credentials.
                    // options.AddDevelopmentEncryptionCertificate()
                    //              .AddDevelopmentSigningCertificate();

                    //Development only
                    options
                        .AddEphemeralEncryptionKey()
                        .AddEphemeralSigningKey()
                        .disableAccesstokenEncryption();

                    // Register scopes (i.e. the modes we can operate in - there may be a better way to do this (different endpoints?)
                    options.RegisterScopes("normal","registration");

                    //Todo: Include Quartz for cleaning up old tokens

                    options.UseAspNetCore()
                        .EnabletokenEndpointPassthrough();

                    options.EnableDegradedMode();   //Activates our custom handlers as the only authentication mechansim,otherwise the workflow attempt to invoke our handler *after* the default ones have already Failed
                                                    //the request
                    options.AddEventHandler<ValidatetokenRequestContext>(builder =>
                       builder.UseInlineHandler(context =>
                       {
                           //Todo: Check that the client Id  is kNown
                           if (!string.Equals(context.ClientId,"client-1",StringComparison.Ordinal))
                           {
                               context.Reject(
                                   error: Errors.InvalidClient,description: "The specified 'client_id' doesn't match a kNown Client ID.");
                               return default;
                           }
                           return default;
                       }));

                    options.AddEventHandler<HandletokenRequestContext>(builder =>
                        builder.UseInlineHandler(context =>
                        {
                            var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType,OpenIddictConstants.Claims.Name,OpenIddictConstants.Claims.Role);
                            identity.AddClaim(OpenIddictConstants.Claims.Subject,context.ClientId,OpenIddictConstants.Destinations.Accesstoken,OpenIddictConstants.Destinations.IdentityToken);

                            if (context.Request.Scope == "registration")
                            {
                                //Todo: Authenticate against BackOffice system to get it's token so we can add it as a claim
                                identity.AddClaim("backoffice_token",Guid.NewGuid().ToString(),OpenIddictConstants.Destinations.Accesstoken);
                            }
                            else
                            {
                                //Todo: Authenticate against internal authentication database as normal
                            }

                            var cp = new ClaimsPrincipal(identity);
                            cp.SetScopes(context.Request.GetScopes());

                            context.Principal = cp;

                            //This doesn't work either
                            //context.SignIn(context.Principal);

                            //ERROR: When this exits the response is empty
                            return default;
                        }));
                });
            //.AddValidation(options =>
            //{
            //    options.UseLocalServer();
            //    options.UseAspNetCore();
            //});

            services.AddControllers();
            services.AddHostedService<CredentialLoader>();
        }

解决方法

最后,我做了 3 处更改:

在 HandleTokenRequestContext 中:

var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);

不确定这是否必要。然后删除

context.Principal = cp;

但重新添加

context.SignIn(context.Principal);