带有 ClientCredentials 流的 OpenIdDict 降级模式

Posted

技术标签:

【中文标题】带有 ClientCredentials 流的 OpenIdDict 降级模式【英文标题】:OpenIdDict Degraded Mode with ClientCredentials flow 【发布时间】:2021-09-27 19:53:07 【问题描述】:

我正在关注这篇关于允许 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>();
        

【问题讨论】:

【参考方案1】:

最后,我做了 3 处改动:

在 HandleTokenRequestContext 中:

var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);

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

context.Principal = cp;

但重新添加

context.SignIn(context.Principal);

【讨论】:

以上是关于带有 ClientCredentials 流的 OpenIdDict 降级模式的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Delphi XE 发送 WCF 的 ClientCredentials

在 App.config 中设置 WCF ClientCredentials

使用自定义 ClientCredentials 进行 WCF 身份验证:要使用的 clientCredentialType 是啥?

无法将 @LoadBalanced 与在 ClientCredentials 上配置的 OAuth2RestTemplate 一起使用

Java I/O流的总结

JAVA中I/O流的使用