3. gRPC 中的 TLS 加密:服务端配置、客户端验证与自签名证书实践

TLS 加密,说白了就是给 gRPC 通信加一把锁。我经历过很多客户,上来就问性能怎么优化,结果一看,连加密都没开。嗯,这其实是个误区。没有 TLS 的 gRPC 就像在明信片上写密码——谁都能看。今天我们就聊聊这把锁怎么装,怎么配,以及怎么在开发环境里自己造一把“假锁”。

3.1 为什么是 TLS,而不是别的?

gRPC 默认支持两种加密机制:TLS 和 ALTS。ALTS 是 Google 内部用的,如果你不是跑在 GCP 上,基本用不着。所以对大多数团队而言,TLS 是唯一的选择。

核心原则:生产环境永远不要禁用 TLS。你可以在内部压测时关闭,但上线前必须开启。

3.2 服务端 TLS 配置

服务端配置 TLS,核心就是搞对证书文件和密钥的路径。我见过最典型的错误是证书格式不对——Go 用 PEM,Java 用 JKS,混用就报错。

3.2.1 文件准备

你需要两个文件:

  • server.crt:服务端证书(公钥 + 身份信息)
  • server.key:服务端私钥(必须保密)

3.2.2 Go 服务端示例

// 加载证书和密钥
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil {
    log.Fatalf("加载证书失败: %v", err)
}

// 创建 gRPC 服务并附加 TLS
s := grpc.NewServer(grpc.Creds(creds))
// ... 注册服务 ...
s.Serve(lis)

避坑指南:我曾经因为证书路径写相对路径导致部署后找不到文件。建议在代码中用环境变量或配置中心管理证书路径,别硬编码。

3.3 客户端验证:双向 TLS 还是单向?

客户端验证分两种:

  • 单向 TLS:客户端验证服务端证书(默认,95% 场景够用)
  • 双向 TLS:客户端也需提供证书,服务端反向验证客户端身份(适合内部微服务高安全场景)

我个人习惯是:内部服务间通信用双向 TLS,对外 API 网关用单向 TLS + 认证。为什么?因为双向 TLS 的证书管理成本高, 在网关上更容易兜底。

3.3.1 客户端配置(单向)

// 加载服务端 CA 证书,用于验证服务端
creds, err := credentials.NewClientTLSFromFile("ca.crt", "")
if err != nil {
    log.Fatalf("加载 CA 失败: %v", err)
}

conn, err := grpc.Dial("server:443", grpc.WithTransportCredentials(creds))

3.3.2 客户端配置(双向)

// 加载客户端证书和密钥
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
    log.Fatalf("加载客户端证书失败: %v", err)
}

// 配置 TLS
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{cert},
    ServerName:   "server.example.com",
}

creds := credentials.NewTLS(tlsConfig)
conn, err := grpc.Dial("server:443", grpc.WithTransportCredentials(creds))

3.4 自签名证书实践:开发环境的“造假术”

开发环境没有 CA 怎么办?自己签。但要注意:自签名证书的 Subject Alternative Name (SAN) 必须填对,否则客户端验证会失败。我以前踩过这个坑,折腾了半天才发现是域名不匹配。

3.4.1 生成自签名证书(步骤)

  1. 生成 CA 私钥和证书:这是你的“自建权威机构”
  2. 生成服务端私钥和证书签名请求
  3. 用 CA 签名服务端证书
  4. 生成客户端证书(如需双向)

3.4.2 OpenSSL 命令示例

# 1. 生成 CA
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt

# 2. 生成服务端 CSR(注意 SAN)
openssl req -new -nodes -key server.key -out server.csr -config openssl.cnf

# 3. 签名服务端证书
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -extensions v3_req -extfile openssl.cnf

提示:配置文件 openssl.cnf 里必须包含 subjectAltName = DNS:localhost, DNS:*.example.com。没有 SAN 的证书在 Go 1.15+ 里会直接被拒。

3.5 核心流程与配置对比

下图展示了 TLS 在 gRPC 中的完整握手过程以及证书的作用链。你仔细看,每一步证书验证都是环环相扣的。

gRPC TLS 握手与证书验证流程 gRPC TLS 握手与证书验证流程 Client Server 1. Client Hello + 支持的密码套件 2. Server 返回证书(含公钥) 验证证书 CA 信任链验证 验证通过 生成预主密钥 3. 用公钥加密预主密钥 解密预主密钥 4. 双方生成会话密钥 说明:自签名证书需手动添加 CA 信任,缺少 SAN 会导致证书验证失败

3.6 常见配置对比表

下表总结了不同场景下的 TLS 配置要点,方便你快速参考:

场景 服务端配置 客户端配置 证书要求
开发/测试 自签名证书 + 任意域名 跳过验证或加载自签 CA 必须包含 SAN
内部服务间 CA 签发证书 双向 TLS 验证 服务名与证书 CN 一致
对外 API 商业 CA 签发 单向 TLS + 域名与证书匹配

技巧:开发环境可以用 grpc.WithInsecure() 跳过 TLS 验证,我建议只在本地调试时用。一旦联调,立刻切回正式配置。

3.7 性能影响:加密的代价

TLS 握手有开销,尤其是 RSA 密钥交换。我个人建议:

  • 使用 ECDHE 密码套件,性能比 RSA 好 3-5 倍
  • 开启 Session Resumption,减少重复握手
  • gRPC 的 长连接 特性天然减轻了握手频率——一次连接多次 RPC,加密成本被摊薄了

避坑指南:我曾经用过 RSA 4096 位密钥,结果压测时 CPU 飙到了 90%。换成 ECDSA P-256,直接降到 30%。证书类型对性能的影响被严重低估了。

3.8 总结要点

  • 必须做:生产环境开启 TLS,不要存侥幸心理
  • 区别对待:开发用自签名,内部用双向,对外用单向 +
  • 注意细节:SAN 字段、证书路径、密码套件选择
  • 性能考量:ECDHE + Session Resumption 是标配

嗯,TLS 配置这块说简单也简单,说复杂也复杂。关键是把基础搞扎实,证书管理做好,后面再加性能优化才有意义。下个知识点我们聊聊更进阶的加密策略。现在,先把你代码里的明文 gRPC 修一下吧。