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

C# gRPC集成ASP.NET Core证书认证遇阻,求排查建议

在ASP.NET Core .NET 6 gRPC中集成证书认证的排查方案

问题背景

我正在尝试在C#的gRPC中集成ASP.NET Core .NET 6的证书认证,此前已按微软官方教程搭建好gRPC应用,未添加认证时运行正常。使用自签名证书,已将其添加到本地用户存储和受信任根存储以避免吊销问题,但添加认证后出现异常,尝试多种排查方法无果,希望获得排查思路与建议。

Kestrel gRPC服务端代码

using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using grpcServerTest.Services;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;

namespace grpcServerTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Additional configuration is required to successfully run gRPC on macOS.
            // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682

            // Add services to the container.
            builder.Services.AddGrpc();
            //builder.Services.AddAuthorization();
            builder.Services.AddAuthentication(
                CertificateAuthenticationDefaults.AuthenticationScheme)
                .AddCertificate(options =>
                {
                    options.AllowedCertificateTypes = CertificateTypes.All;
                    options.ValidateCertificateUse = false;
                    options.RevocationFlag = System.Security.Cryptography.X509Certificates.X509RevocationFlag.ExcludeRoot;
                    options.RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck;
                    options.Events = new CertificateAuthenticationEvents
                    {
                        OnChallenge = context =>
                        {
                            return Task.CompletedTask;
                        },
                        OnAuthenticationFailed = context =>
                        {

                            return Task.CompletedTask;
                        },
                        OnCertificateValidated = context =>
                        {

                            if (true)
                            {
                                var claims = new[]
                                {
                                    new Claim(
                                        ClaimTypes.NameIdentifier,
                                        context.ClientCertificate.Subject,
                                        ClaimValueTypes.String, context.Options.ClaimsIssuer),
                                    new Claim(
                                        ClaimTypes.Name,
                                        context.ClientCertificate.Subject,
                                        ClaimValueTypes.String, context.Options.ClaimsIssuer)
                                };

                                context.Principal = new ClaimsPrincipal(
                                    new ClaimsIdentity(claims, context.Scheme.Name));
                                context.Success();
                            }

                            return Task.CompletedTask;
                        }
                    };
                });

            builder.Services.Configure<KestrelServerOptions>(options =>
            {
                options.ConfigureHttpsDefaults(options =>
                {
                    options.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
                    options.CheckCertificateRevocation = false;
                    //options.ServerCertificate = GetClientCertificate();
                    options.ClientCertificateValidation = (cert, chain, errors) =>
                    {
                        Console.WriteLine("Client Validation Called");
                        errors = System.Net.Security.SslPolicyErrors.None;
                        return true;
                    };
                });
            });

            var app = builder.Build();

            app.UseCertificateForwarding();

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

            // Configure the HTTP request pipeline.
            app.MapGrpcService<GreeterService>();
            app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
        
            app.Run();
        }
    }
}

gRPC客户端代码

// See https://aka.ms/new-console-template for more information
using Grpc.Core;
using Grpc.Net.Client;
using GrpcTest;
using System.Security.Cryptography.X509Certificates;

internal class Program
{
    private static async Task Main(string[] args)
    {
        Console.WriteLine("Hello, World!");

        var x509 = GetClientCertificate();

        var handler = new HttpClientHandler();

        handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls;
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        handler.ClientCertificates.Add(x509);
        handler.UseProxy = false;

        Console.ReadKey();
        using var channel = GrpcChannel.ForAddress("https://localhost:7283", new GrpcChannelOptions()
        {
            HttpHandler = handler,
            DisposeHttpClient = true
        });

        var client = new Greeter.GreeterClient(channel);
        var reply = await client.SayHelloAsync(
                          new HelloRequest { Name = "GreeterClient" });
        Console.WriteLine("Greeting: " + reply.Message);
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }

    private static X509Certificate2 GetClientCertificate()
    {
        X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        try
        {
            userCaStore.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
            X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySubjectName, "grpctest", true);
            X509Certificate2 clientCertificate = null!;
            if (findResult.Count == 1)
            {
                clientCertificate = findResult[0];
            }
            else
            {
                throw new Exception("Unable to locate the correct client certificate.");
            }
            return clientCertificate;
        }
        catch
        {
            throw;
        }
        finally
        {
            userCaStore.Close();
        }
    }
}

排查思路与建议

  • 确认证书是否成功传递:在服务端ClientCertificateValidation委托中添加证书信息打印,比如Console.WriteLine($"收到客户端证书主题: {cert?.Subject ?? "无证书"}");,如果输出“无证书”,说明客户端未正确发送证书,需检查客户端证书获取逻辑(如FindBySubjectName的参数是否匹配证书主题,证书是否在CurrentUser/My存储区)。
  • 强制Kestrel要求客户端证书:在服务端ConfigureHttpsDefaults中添加options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;,默认Kestrel不会主动要求客户端提供证书,这会导致认证流程无法触发。
  • 输出认证失败详情:修改服务端OnAuthenticationFailed事件,打印异常信息:
    OnAuthenticationFailed = context =>
    {
        Console.WriteLine($"认证失败: {context.Exception.Message}\n{context.Exception.StackTrace}");
        return Task.CompletedTask;
    }
    
    这能直接定位认证失败的具体原因。
  • 验证客户端证书有效性:在客户端获取证书后,打印x509.HasPrivateKey和x509.Subject,确保证书包含私钥且主题匹配,gRPC双向认证需要带私钥的客户端证书。
  • 统一TLS协议版本:客户端将SslProtocols改为System.Security.Authentication.SslProtocols.Tls12,与服务端保持一致,避免协议不兼容问题。
  • 排查证书信任链:在服务端ClientCertificateValidation中打印证书链状态:
    foreach (var status in chain.ChainStatus)
    {
        Console.WriteLine($"证书链状态: {status.StatusInformation}");
    }
    
    即使关闭吊销检查,信任链不完整也会导致验证问题。
  • 隔离gRPC测试:先用普通HttpClient测试证书传递:
    using var httpClient = new HttpClient(handler);
    var response = await httpClient.GetAsync("https://localhost:7283/");
    Console.WriteLine($"HTTP请求状态码: {response.StatusCode}");
    
    如果HTTP请求也失败,说明问题出在TLS层而非gRPC本身。
  • 检查配置文件冲突:确认appsettings.json中的Kestrel配置没有覆盖代码中的设置,比如是否指定了服务器证书、客户端证书模式等。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:15:43