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

使用DER证书与PEM私钥的SignalR服务器无法连接的原因排查

SignalR使用DER证书+PEM私钥时客户端连接失败问题解决

问题现象

当SignalR服务器使用PFX格式证书时,客户端可正常触发CheckCertificateCallback并成功连接;但切换为DER证书搭配PEM私钥时,服务器运行正常,客户端无法进入CheckCertificateCallback,调用StartAsync后直接报错。

核心问题分析

  1. 客户端URL格式错误:客户端代码中SignalR地址缺少//,导致请求无法正确解析,直接抛出异常,根本无法进入SSL证书验证环节。
  2. 服务端证书加载不完整:使用CopyWithPrivateKey生成的证书,在Kestrel中可能存在私钥关联不彻底的问题,导致SSL握手在服务器端提前失败。
  3. 回调注册方式潜在问题:使用+=注册回调可能存在委托叠加或未正确生效的情况。

解决方案

1. 修复客户端URL格式

将客户端WithUrl中的地址修正为标准HTTPS格式:

_hubConnection = new HubConnectionBuilder()
    .WithUrl("https://10.224.10.10:12316/proxyHub", (options) =>
    {
        // 其余配置不变
    })
    .WithAutomaticReconnect(new RandomRetryPolicy())
    .Build();

2. 修正服务端证书加载逻辑

修改CertificateMgr.cs中的GetCertificate方法,将生成的证书导出为PFX再重新加载,确保私钥完整关联:

else
{
    StreamReader sr = new StreamReader(_keyPath);
    string privateKeyPass = "123123";
    var pf = new PasswordFinder(privateKeyPass);
    PemReader pr = new PemReader(sr, pf);
    AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
    RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
    using (RSA rsa = RSA.Create())
    {
        rsa.ImportParameters(rsaParameters);
        X509Certificate2 certificate = new X509Certificate2(_certPath);
        certificate = certificate.CopyWithPrivateKey(rsa);
        // 导出为PFX并重新加载,确保私钥有效
        var pfxBytes = certificate.Export(X509ContentType.Pfx, privateKeyPass);
        return new X509Certificate2(pfxBytes, privateKeyPass, 
            X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
    }
}

3. 优化客户端回调注册方式

将回调注册改为直接赋值,避免委托叠加问题:

options.HttpMessageHandlerFactory = (handler) =>
{
    if (handler is HttpClientHandler clientHandler)
    {
        // 替换为直接赋值
        clientHandler.ServerCertificateCustomValidationCallback = CheckCertificateCallback;
    }
    return handler;
};

4. 开启服务端SSL日志排查(可选)

若问题仍存在,开启Kestrel的SSL日志定位握手失败原因:

builder.WebHost.UseKestrel(options =>
{
    options.ListenAnyIP(12316, config =>
    {
        var certificateMgr = new CertificateMgr();
        var certificate = certificateMgr.GetCertificate();
        if (certificate != null)
        {
            config.UseHttps(certificate);
        }
    });
    // 开启SSL调试日志
    options.ConfigureLogging(logging =>
    {
        logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel.Https", LogLevel.Debug);
        logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Debug);
    });
});

附:相关代码完整片段

服务端Program.cs

using SignalRService.Common;
using SignalRService.Hubs;
using SignalRService.Providers;
using SignalRService.Services;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWindowsService(options =>
{
    options.ServiceName = "SignalR Server Service";
});
builder.WebHost.UseKestrel(options =>
{
    options.ListenAnyIP(12316, config =>
    {
        var certificateMgr = new CertificateMgr();
        var certificate = certificateMgr.GetCertificate();
        if (certificate != null)
        {
            config.UseHttps(certificate);
        }
    });
    options.ConfigureLogging(logging =>
    {
        logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel.Https", LogLevel.Debug);
        logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Debug);
    });
});
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "AllowAll", builder =>
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader());
});
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = true;
});
builder.Services
    .AddSingleton<IUserIdProvider, UserIdProvider>()
    .AddHostedService<ServerService>();

var app = builder.Build();
app.UseRouting();
app.UseCors("AllowAll");
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ProxyHub>("proxyHub").RequireCors("AllowAll");
});
await app.RunAsync();

客户端Client.cs关键修改片段

public async Task HubConnection_StartAsync()
{
    _hubConnection = new HubConnectionBuilder()
        .WithUrl("https://10.224.10.10:12316/proxyHub", (options) =>
        {
            options.HttpMessageHandlerFactory = (handler) =>
            {
                if (handler is HttpClientHandler clientHandler)
                {
                    clientHandler.ServerCertificateCustomValidationCallback = CheckCertificateCallback;
                }
                return handler;
            };
        })
        .WithAutomaticReconnect(new RandomRetryPolicy())
        .Build();
    // 其余事件注册和启动逻辑不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 07:02:03