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 生成自签名证书(步骤)
- 生成 CA 私钥和证书:这是你的“自建权威机构”
- 生成服务端私钥和证书签名请求
- 用 CA 签名服务端证书
- 生成客户端证书(如需双向)
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 中的完整握手过程以及证书的作用链。你仔细看,每一步证书验证都是环环相扣的。
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 修一下吧。