如何在客户端验证中忽略自签名证书问题

How to ignore Self Signed Certificate issue in client validation

我一直在尝试研究如何禁用用于向授权元数据端点发送请求的 HttpClient 的 SSL 证书验证。我是 运行 本地授权服务器,使用主机名 idp.local.test.com 和 haproxy 作为使用自签名证书的反向代理。

当我在本地测试客户端令牌验证(无自省)时,用于向元数据端点发送 GET 请求的 HttpClient 抛出 SSL 验证错误,因为我使用的是自签名证书。

这是日志的输出:

info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[100]
      Sending HTTP request GET https://idp.local.test.com/.well-known/openid-configuration
System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler: Information: Sending HTTP request GET https://idp.local.test.com/.well-known/openid-configuration
Loaded '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/3.1.5/System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/3.1.5/System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
dbug: OpenIddict.Validation.OpenIddictValidationDispatcher[0]
      An exception was thrown by OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlers+SendHttpRequest`1[[OpenIddict.Validation.OpenIddictValidationEvents+ApplyConfigurationRequestContext, OpenIddict.Validation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=35a561290d20de2f]] while handling the OpenIddict.Validation.OpenIddictValidationEvents+ApplyConfigurationRequestContext event.
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslStream.ThrowIfExceptional()
   at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__65_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)

这是客户端应用程序中的启动 class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    });

    services.AddOpenIddict()
        .AddValidation(options =>
        {
            // options.Configure(config =>
            // {
            //     config.MetadataAddress = new Uri("/.well-known/openid-configuration");
            // });
            var section = Configuration.GetSection("OAuth");
            var tokenEncryptionKey = section.GetValue<string>("TokenEncryptionKey");
            var issuer = section.GetValue<string>("Issuer");

            options.SetIssuer(issuer);
            options.AddEncryptionKey(new SymmetricSecurityKey(
                Convert.FromBase64String(tokenEncryptionKey)
            ));
            
            options.UseSystemNetHttp();
            options.UseAspNetCore();
        });

    services.AddControllers(options =>
    {
        options.Filters.Add(typeof(GlobalExceptionFilter));
        var requireAuthPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(requireAuthPolicy));
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });

}

这是授权服务器代码:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddPasswordlessLoginTokenProvider()
    .AddEmailConfirmationTokenProvider()
    .AddPasswordResetTokenProvider();

    services.AddOpenIddict()
    .AddCore(coreBuilder => 
    {
        coreBuilder.SetDefaultApplicationEntity<OIDCApplication>()
            .UseEntityFrameworkCore()
            .UseDbContext<ApplicationDbContext>();
    })
    .AddServer(serverBuilder => 
    {   
        serverBuilder.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess);
        serverBuilder.SetAuthorizationEndpointUris("/connect/authorize")
            .SetTokenEndpointUris("/connect/token")
            .SetConfigurationEndpointUris("/.well-known/openid-configuration")
            .SetUserinfoEndpointUris("/connect/userinfo")
            .SetIntrospectionEndpointUris("/connect/introspect");

        serverBuilder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(5));

        string issuerHostname = Configuration["IssuerHost"];
        serverBuilder.SetIssuer(new Uri($"https://{issuerHostname}"));
        serverBuilder.Configure(options => 
        {
            options.UseSlidingExpiration = true;
        });

        serverBuilder.AllowAuthorizationCodeFlow()
            .AllowRefreshTokenFlow();

        serverBuilder.UseAspNetCore()
            .EnableAuthorizationEndpointPassthrough()
            .EnableTokenEndpointPassthrough()
            .EnableUserinfoEndpointPassthrough()
            .DisableTransportSecurityRequirement(); // Remove on prod

        var tokenEncryptionKey = Configuration.GetValue<string>("TokenEncryptionKey");
        serverBuilder.AddEncryptionKey(new SymmetricSecurityKey(
            Convert.FromBase64String(tokenEncryptionKey)
        ));

        serverBuilder.AddDevelopmentSigningCertificate();
    })
    .AddValidation(options =>
    {
        options.UseLocalServer();

        options.UseAspNetCore();
    });
    
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHsts();

    app.UseMiddleware<Middlewares.LoggingMiddleware>();

    app.UseExceptionHandler("/Home/Error");
    app.UseStatusCodePagesWithReExecute("/Home/Status", "?code={0}");
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

干杯

将您的自签名证书添加到受信任的证书列表中是正确的做法(这主要取决于您的 OS)。

或者,您可以使用 HttpClientFactory API 强制 OpenIddict System.Net.Http 集成使用的 HttpClientHandler 忽略服务器证书验证错误:

services.AddHttpClient(typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName().Name)
    .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
    });