当前位置: 首页 > 知识库问答 >
问题:

Grpc。Net客户端无法使用SSL连接到服务器

瞿宏儒
2023-03-14

无法连接到本链接中提到的greeter grpc服务-https://docs . Microsoft . com/en-us/aspnet/core/tutorials/grpc/grpc-start?欢迎客户端的view=aspnetcore-3.0,它是使用grpc.core库(< code>Grpc)从. net framework应用程序编写的。核心2.24.0和< code>Grpc。Core.Api.2.24.0)。

下面是我的客户端代码。它适用于非SSL,但不适用于SSL

使用非SSL的客户端代码(这是可行的)

var channel = new Channel("localhost:5000", ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

使用SSL的客户端代码(此连接失败)

SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

我使用SSL得到的错误是:

Grpc.Core.RpcException:'状态(StatusCode=不可用, Detail="无法连接到所有地址")'

我尝试了同一链接中提到的.net核心应用程序客户端(https://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0),它与SSL和非SSL一起工作,但不直接使用grp库。我的客户端是.Net framework客户端,这就是我无法使用.Net库连接到grpc服务的原因。Net核心应用程序仅支持.Net grpc库。

SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();

预期结果-来自服务器的响应

实际结果-<code>Grpc.Core。RPCEException:“状态(状态代码=不可用,详细信息=无法连接到所有地址”)

共有3个答案

孙永思
2023-03-14

我在不保存客户端的pem的情况下使其工作(如果客户端和服务器在不同的机器上,则发生事件)。

首先,非常重要的是,目标/主机名(用于创建通道的名称)必须与服务器证书中的CN(通用名称)相匹配,这里需要注意的是它区分大小写!

e.q: 证书的 CN 是 SV-XXX-DEV-01,您指定 sv-xxx-dev-01 这将不起作用,并且您会收到以下错误:

Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'

所以下面是我的解决方案(当然这个可以优化,不应该在一个类里(关注点分离),但是更容易抓住重点。

    public static async Task Main(string[] args)
            {
                await FullFrameworkSample();
            }
    
            private static async Task FullFrameworkSample()
            {
                Uri host = new Uri("https://sv-xxx-dev-cpu-01:44301");
                int port = host.Port;
    
                (string publicKeyInPemFormat, string commonName) = await GetCertificateInformationFromServer(host);
    
                //note: in the full framework implementation it's very important that the casing of the target is correct (the same casing as in the CN name of the certificate)
                string target = $"{commonName}:{port}";
    
                //note: thats only needed in our case, because we have a server side interceptor, that checks if the secureKey is valid.
                CallCredentials credentials = CallCredentials.FromInterceptor((context, metadata) =>
                                                                              {
                                                                                  metadata.Add("SecurityTokenId", "someSecureKey");
    
                                                                                  return Task.CompletedTask;
                                                                              });
    
                ChannelCredentials channelCredentials = ChannelCredentials.Create(new SslCredentials(publicKeyInPemFormat), credentials);
    
                Channel channel = new Channel(target, channelCredentials);
    
                ProjectInlayDataService.ProjectInlayDataServiceClient client = new ProjectInlayDataService.ProjectInlayDataServiceClient(channel);
    
                GetProjectInlayDataResponse result = await client.GetProjectInlayDataAsync(new GetProjectInlayDataRequest());                                                                                        
    
                await channel.ShutdownAsync();
    
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
    
            private static async Task<(string PublicKeyInPemFormat, string CommonName)> GetCertificateInformationFromServer(Uri host)
            {
                Regex commonNameRegex = new Regex("CN=([\\w\\-.]*),?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    
                StringBuilder builder = new StringBuilder();
                const string newline = "\n";
    
                X509Certificate certFromServer;
    
                using (HttpClient client = new HttpClient())
                {
                    using (HttpResponseMessage _ = await client.GetAsync(host))
                    {
                       //get the certificate from the server, so we don't need to store the pem.
                        certFromServer = ServicePointManager.FindServicePoint(host).Certificate;
                        if (certFromServer == null)
                            throw new InvalidOperationException($"Could not get certificate from server ({host}).");
                    }
                }
    
                Match match = commonNameRegex.Match(certFromServer.Subject);
                if (!match.Success)
                    throw new InvalidOperationException($"Could not extract CN (Common Name) from server certificate ({certFromServer.Subject}).");
    
                string commonName = match.Groups[1].Captures[0].Value;
    
                X509Certificate2 certificate = new X509Certificate2(certFromServer);
                string pem = ExportToPem(certificate);
    
                builder.AppendLine(
                    "# Issuer: " + certificate.Issuer + newline +
                    "# Subject: " + certificate.Subject + newline +
                    "# Label: " + certificate.FriendlyName + newline +
                    "# Serial: " + certificate.SerialNumber + newline +
                    "# SHA1 Fingerprint: " + certificate.GetCertHashString() + newline +
                    pem + newline);
    
                return (builder.ToString(), commonName);
            }
    
            /// <summary>
            /// Export a certificate to a PEM format string
            /// </summary>
            /// <param name="cert">The certificate to export</param>
            /// <returns>A PEM encoded string</returns>
            private static string ExportToPem(X509Certificate cert)
            {
                StringBuilder builder = new StringBuilder();

                builder.AppendLine("-----BEGIN CERTIFICATE-----");           
        
 builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
            builder.AppendLine("-----END CERTIFICATE-----");

                return builder.ToString();
            }
    }
杜君浩
2023-03-14

我通过在客户端使用服务器的pem格式的证书来使用SSL端口。

SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));
var channel = new Channel("localhost", 5001, secureCredentials);

稍作说明,VS 2019中的Asp.NETCore模板使用带有pfx文件的开发证书,位于%AppData%\ASP.NET\Https\ProjectName.pfx和密码=%AppData%\Microsoft\UserSecrets\{UserSecresId}\secrets.json{: Kestrel:证书:开发:密码}值您可以从ProjectName.csproj获取UserSecresIdid。这对于每个ASP.NET核心项目都不同。

我们只需要一个< code>certificate.pem文件形式的证书公钥,就可以通过gRPC进行安全通信。使用下面的命令从pfx提取publickey

openssl pkcs12 -in "<DiskLocationOfPfx>\ProjectName.pfx" -nokeys -out "<TargetLocation>\certifcate.pem"

复制此证书。供gRPC.NET Framework客户端使用的pem。

SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("<DiskLocationTo the Folder>/certificate.pem"))
var channel = new Channel("localhost", 5001, secureCredentials);

请注意,我使用的端口 5001 是我 ASP.NET 核心应用程序的 SSL 端口。

对于生产场景

使用来自证书签名颁发机构的有效证书,并在 ASP.NET 核心服务器和 .NET Framework 客户端中分别使用与 pfx 和 pem 相同的证书。

或使用自签名证书

对于在我们自己的微服务之间通信的大多数微服务来说,使用自签名证书是一种有效的选择。我们可能不需要权威机构签署的证书。使用自签名证书可能会遇到的一个问题是,证书可能会颁发给某个目标DNS名称,我们的gRPC服务器可能正在其他地方运行,无法建立安全连接。

使用gRPC目标名称覆盖密钥覆盖ssl目标名称验证。

   List<ChannelOption> channelOptions = new List<ChannelOption>()
   {
       new ChannelOption("grpc.ssl_target_name_override", <DNS to which our certificate is issued to>),
   };
   SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));

   var channel = new Channel("localhost", 5001, secureCredentials, channelOptions);
郎曾笑
2023-03-14

我做了一个工作客户。在. NET Framework c上运行服务器。本地主机上的NET Core:

static async Task Main(string[] args)
{    
    string s = GetRootCertificates();
    var channel_creds = new SslCredentials(s);
    var channel = new Channel("localhost",50051, channel_creds);
    var client = new Informer.InformerClient(channel);
    await GetPing(client);
}

public static string GetRootCertificates()
{
    StringBuilder builder = new StringBuilder();
    X509Store store = new X509Store(StoreName.Root);
    store.Open(OpenFlags.ReadOnly);
    foreach (X509Certificate2 mCert in store.Certificates)
    {
        builder.AppendLine(
            "# Issuer: " + mCert.Issuer.ToString() + "\n" +
            "# Subject: " + mCert.Subject.ToString() + "\n" +
            "# Label: " + mCert.FriendlyName.ToString() + "\n" +
            "# Serial: " + mCert.SerialNumber.ToString() + "\n" +
            "# SHA1 Fingerprint: " + mCert.GetCertHashString().ToString() + "\n" +
            ExportToPEM(mCert) + "\n");
    }
    return builder.ToString();
}

/// <summary>
/// Export a certificate to a PEM format string
/// </summary>
/// <param name="cert">The certificate to export</param>
/// <returns>A PEM encoded string</returns>
public static string ExportToPEM(X509Certificate cert)
{
    StringBuilder builder = new StringBuilder();            

    builder.AppendLine("-----BEGIN CERTIFICATE-----");
    builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
    builder.AppendLine("-----END CERTIFICATE-----");

    return builder.ToString();
}

private static async Task GetPing(Informer.InformerClient client)
{
    Console.WriteLine("Getting ping...");
    try
    {
        Metadata headers = null;
        var response = await client.GetServerPingAsync(new Empty(), headers);
        string result = "Nan";
        if (response.PingResponse_ == 1)
            result = "Ok!";
        Console.WriteLine($"Ping say: {result }");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error get server ping." + Environment.NewLine + ex.ToString());
    }
}

但是我还没有成功地在远程计算机上完成这项工作(例如,其中ip 192.168.1.7是服务器地址,客户端地址是192.168.1.2)。

 类似资料:
  • 我无法连接到我的节点。js服务器。当通过节点使用http web服务器在本地运行它时,它工作得很好,但是当连接到外部时,它会加载<code>socket.io。js文件很好,但当尝试使用套接字时,它会从URL中删除端口,无法连接。 而不是在网络请求中执行此操作: http://external-domain.com:3000/socket.io/?EIO=3 它是这样做的: http://exte

  • 我已经看了很长一段时间了,在互联网上浏览了所有不同的帖子,看看是否能找到有相同问题的人,但不知为什么,我似乎是唯一有这个问题的人。 我有一个ASP。net核心服务器,运行Grpc的基本迎宾服务。使用程序“BloomRPG”调用此服务器效果很好,而且响应时间也很长。然而,当我试图从自己编写的(非常基本的)客户机上执行同样的操作时,我会遇到以下错误: 在客户机上: dbug:Grpc。网客户内部的Gr

  • 问题内容: 我完全被困在这里。我有一个Java客户端代码,需要使用自签名证书连接到SSL服务器。 仅 当我在服务器端禁用SSLv2支持时, 才会 出现此问题。 痕迹是 在服务器端,我可以看到以下跟踪: 如果启用SSL2,我会看到 而且一切正常。 我还知道那不是服务器端的东西,因为与其他软件连接可以正常工作。 知道我在做什么错吗? 还有人知道这个“读取客户端问候A / B”是什么意思吗? 谢谢 更新

  • 将grpc定义的服务视为: 并利用客户端连接到此服务类似于: 如果我们要在一个单独的线程中生成调用,那么处理终止一个永远运行、我们不想再使用的grpc连接的正确方法是什么?是否有任何连接或流控制方法可以调用?

  • 我正在尝试在GKE上部署gRPC,我遵循了本教程-https://cloud.google.com/solutions/exposing-grpc-services-on-gke-using-envoy-proxy 我完成了所有工作,但我似乎无法在golang上运行gRPC,而我可以在grpcurl上运行它。 有人有什么想法吗?

  • 所有的 我正在尝试连接到Oracle 19C数据库。我安装了两个Oracle客户端(11g和12c),因为我们需要支持遗留程序。我可以通过12c客户端与使用sqlplus的任何用户连接,没有问题。但是如果我与任何用户一起使用11g(11.2.0)客户端。我总是得到: 两个客户端都有完全相同的sqlnet。ora和tnsnames。ora文件,因此两个客户端都指向同一个数据库。 有什么想法吗?我是否