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

.NET 7 gRPC服务配置客户端证书必填未生效,兼问双认证可行性

同时使用客户端证书与JWT令牌的gRPC服务认证问题

我有一套gRPC服务与客户端,希望通过客户端证书实现仅指定应用可调用服务,防止第三方开发的应用调用;同时用JWT令牌识别用户,区分不同用户的权限。

我的gRPC服务基于.NET 7的ASP Core应用托管,以下是program.cs代码:

Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;

var builder = WebApplication.CreateBuilder(args);

var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();

try
{
    builder.WebHost.ConfigureKestrel((context, options) =>
    {
        string miStrCertificado = File.ReadAllText(builder.Configuration.GetSection("Certificados:Certificado").Value!);
        string miStrKey = File.ReadAllText(builder.Configuration.GetSection("Certificados:Key").Value!);
        X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);

        X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));

        miCertficadoX509.Dispose();

        options.ListenAnyIP(Convert.ToInt32(builder.Configuration.GetSection("Servidor:Puerto").Value!), listenOptions =>
        {
            listenOptions.Protocols = HttpProtocols.Http2;
            listenOptions.UseHttps(miCertificado2);
        });

        options.ConfigureHttpsDefaults(miHttpsOptions =>
        {
            miHttpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
            miHttpsOptions.ServerCertificate = miCertificado2;
        });
    });

    builder.Services.AddHttpContextAccessor();

    builder.Services.AddGrpc();
    builder.Services.AddCodeFirstGrpc();

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(builder.Configuration.GetSection("JwtAuthApp.Server:JwtTokenService:ClaveCifradoToken").Value!)),
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                ClockSkew = TimeSpan.FromSeconds(10),

                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
            };
        });

    builder.Services.AddAuthorization();

    var app = builder.Build();

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapGrpcService<MyGrpcService>();

    await app.RunAsync();
}
catch (Exception ex)
{
    Console.Write($"ERROR: {ex.Message}");
}

如上述代码所示,我已在Kestrel的ConfigureHttpsDefaults中设置了ClientCertificateMode.RequireCertificate,但测试时客户端未携带证书仍能收到服务响应,预期会触发认证异常。请问是否无法同时使用客户端证书与JWT令牌这两种认证方式?


解决方案:完全可以同时使用两种认证方式,问题出在配置逻辑缺失

1. 修正Kestrel客户端证书配置优先级问题

你在ListenAnyIP中单独调用UseHttps(miCertificado2)会覆盖ConfigureHttpsDefaults中的客户端证书要求,需要将证书验证逻辑直接嵌入监听配置:

builder.WebHost.ConfigureKestrel((context, options) =>
{
    string miStrCertificado = File.ReadAllText(builder.Configuration.GetSection("Certificados:Certificado").Value!);
    string miStrKey = File.ReadAllText(builder.Configuration.GetSection("Certificados:Key").Value!);
    X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);

    X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));
    miCertficadoX509.Dispose();

    options.ListenAnyIP(Convert.ToInt32(builder.Configuration.GetSection("Servidor:Puerto").Value!), listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http2;
        listenOptions.UseHttps(httpsOptions =>
        {
            httpsOptions.ServerCertificate = miCertificado2;
            // 直接在监听配置中要求客户端证书
            httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
        });
    });
});

2. 注册客户端证书认证服务并配置双重认证策略

仅要求证书还不够,需要将客户端证书纳入ASP.NET Core认证体系,与JWT形成双重验证:

// 注册双重认证服务
builder.Services.AddAuthentication()
    // JWT认证配置
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration.GetSection("JwtAuthApp.Server:JwtTokenService:ClaveCifradoToken").Value!)),
            RequireExpirationTime = true,
            RequireSignedTokens = true,
            ClockSkew = TimeSpan.FromSeconds(10),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
        };
        // 适配gRPC的令牌提取逻辑
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var token = context.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last();
                if (!string.IsNullOrEmpty(token))
                {
                    context.Token = token;
                }
                return Task.CompletedTask;
            }
        };
    })
    // 客户端证书认证配置
    .AddCertificate(options =>
    {
        options.AllowedCertificateTypes = CertificateTypes.All;
        options.Events = new CertificateAuthenticationEvents
        {
            OnCertificateValidated = context =>
            {
                // 验证证书是否为你指定的合法证书(示例:校验指纹)
                var validThumbprints = builder.Configuration.GetSection("Certificados:ValidThumbprints").Get<string[]>();
                if (validThumbprints != null && validThumbprints.Contains(context.ClientCertificate.Thumbprint))
                {
                    var claims = new[]
                    {
                        new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject),
                        new Claim("CertificateThumbprint", context.ClientCertificate.Thumbprint)
                    };
                    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
                    context.Success();
                }
                else
                {
                    context.Fail("无效的客户端证书");
                }
                return Task.CompletedTask;
            }
        };
    });

// 配置授权策略:要求同时通过两种认证
builder.Services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, CertificateAuthenticationDefaults.AuthenticationScheme)
        .Build();
});

3. 为gRPC服务添加授权标记

确保你的gRPC服务类上添加[Authorize]特性,强制应用双重认证策略:

[Authorize]
public class MyGrpcService : MyGrpc.MyGrpcBase
{
    // 你的服务实现方法
}

关键说明

  • 客户端证书认证:在TLS握手阶段完成,确保只有持有合法证书的应用能建立连接,实现应用级访问控制。
  • JWT令牌认证:在应用层完成,识别具体用户并验证权限,实现用户级访问控制。
  • 两种认证方式互不冲突,通过授权策略可强制要求请求同时满足两种认证条件,实现双重验证。

内容的提问来源于stack exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:04:56