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测试证书传递:
如果HTTP请求也失败,说明问题出在TLS层而非gRPC本身。using var httpClient = new HttpClient(handler); var response = await httpClient.GetAsync("https://localhost:7283/"); Console.WriteLine($"HTTP请求状态码: {response.StatusCode}"); - 检查配置文件冲突:确认
appsettings.json中的Kestrel配置没有覆盖代码中的设置,比如是否指定了服务器证书、客户端证书模式等。
内容的提问来源于stack exchange,提问作者Matthew
相关产品推荐
相关产品推荐

