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

问题描述

我一直在尝试研究如何通过用于向授权元数据端点发送请求的 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,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.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)

这是客户端应用程序中的 Startup 类:

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?}");
    });
}

干杯

解决方法

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

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

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