gRPC 安全体系
gRPC 的安全机制分为两层,各自解决不同问题:
| 安全层 | 机制 | 解决的问题 |
|---|---|---|
| 传输安全 | TLS / mTLS | 加密传输、防止中间人攻击、服务身份验证 |
| 调用认证 | Metadata + JWT/Token | 用户身份验证、权限控制 |
生成测试证书
# 生成 CA 私钥和自签名证书
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/C=CN/O=MyApp/CN=MyApp CA"
# 生成服务端私钥和 CSR
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/O=MyApp/CN=product-service"
# 用 CA 签发服务端证书(含 SAN)
cat > server-ext.cnf <<EOF
[SAN]
subjectAltName=DNS:localhost,IP:127.0.0.1
EOF
openssl x509 -req -days 365 -in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -extfile server-ext.cnf -extensions SAN
# 生成客户端证书(mTLS 用)
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr \
-subj "/C=CN/O=MyApp/CN=order-service"
openssl x509 -req -days 365 -in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
TLS:服务端单向 TLS
// 服务端:加载证书
import "google.golang.org/grpc/credentials"
func main() {
// 加载服务端证书和私钥
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatalf("load cert: %v", err)
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}
creds := credentials.NewTLS(tlsConfig)
s := grpc.NewServer(grpc.Creds(creds))
// ... 注册服务
}
// 客户端:使用 CA 证书验证服务端
func newTLSClient() *grpc.ClientConn {
caCert, _ := os.ReadFile("ca.crt")
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
RootCAs: certPool,
ServerName: "product-service", // 必须匹配证书 CN/SAN
MinVersion: tls.VersionTLS13,
}
creds := credentials.NewTLS(tlsConfig)
conn, err := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("connect: %v", err)
}
return conn
}
mTLS:双向认证
mTLS(Mutual TLS)要求客户端也提供证书,服务端验证客户端身份。这是微服务间认证的黄金标准,确保只有受信任的服务才能访问。
// 服务端:要求并验证客户端证书
func newMTLSServer() *grpc.Server {
cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
caCert, _ := os.ReadFile("ca.crt")
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientCAs: certPool,
ClientAuth: tls.RequireAndVerifyClientCert, // 关键:要求客户端证书
MinVersion: tls.VersionTLS13,
}
creds := credentials.NewTLS(tlsConfig)
return grpc.NewServer(grpc.Creds(creds))
}
// 客户端:携带自己的证书
func newMTLSClient() *grpc.ClientConn {
clientCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
caCert, _ := os.ReadFile("ca.crt")
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert}, // 客户端自己的证书
RootCAs: certPool,
ServerName: "product-service",
MinVersion: tls.VersionTLS13,
}
creds := credentials.NewTLS(tlsConfig)
conn, _ := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(creds))
return conn
}
Metadata:传递认证 Token
Metadata 是 gRPC 的"HTTP Header",以键值对方式传递横切信息,如 Authorization、Request-ID、Trace-ID。
// 客户端:发送 Metadata
import "google.golang.org/grpc/metadata"
ctx := context.Background()
// 方式1:通过 metadata.AppendToOutgoingContext
ctx = metadata.AppendToOutgoingContext(ctx,
"authorization", "Bearer eyJhbGciOiJIUzI1NiJ9...",
"x-request-id", "550e8400-e29b-41d4-a716-446655440000",
)
// 方式2:通过 metadata.NewOutgoingContext(替换全部)
md := metadata.Pairs(
"authorization", "Bearer token...",
)
ctx = metadata.NewOutgoingContext(ctx, md)
resp, err := client.GetProduct(ctx, req)
// 服务端:读取 Metadata
func (s *server) GetProduct(
ctx context.Context,
req *pb.GetProductRequest,
) (*pb.ProductResponse, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "no metadata")
}
// key 自动转小写
authHeaders := md.Get("authorization")
requestIDs := md.Get("x-request-id")
// ...
}
PerRPCCredentials:每次调用附加凭证
PerRPCCredentials 接口让你在每次 RPC 调用时动态附加凭证(如动态刷新的 JWT Token)。
// 实现 PerRPCCredentials 接口
type tokenCredentials struct {
tokenFn func() string // 动态获取 token 的函数
}
func (t *tokenCredentials) GetRequestMetadata(
ctx context.Context, uri ...string,
) (map[string]string, error) {
return map[string]string{
"authorization": "Bearer " + t.tokenFn(),
}, nil
}
func (t *tokenCredentials) RequireTransportSecurity() bool {
return true // 生产环境要求 TLS
}
// 注册到 Client Connection
conn, _ := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(&tokenCredentials{
tokenFn: tokenStore.GetAccessToken,
}),
)
与 Istio mTLS 集成
在 Kubernetes 环境中,Istio 服务网格可以在无需修改应用代码的情况下,自动为所有服务间通信启用 mTLS。
# Istio PeerAuthentication:启用严格 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT # 只接受 mTLS 连接,拒绝明文
Istio 接管 mTLS 后应用代码怎么写?:Istio sidecar(Envoy)在 Pod 网络层透明处理 mTLS,应用代码继续使用 insecure.NewCredentials() 即可——因为流量在 Pod 内部(app → sidecar)是明文,在 Pod 外部(sidecar → sidecar)由 Istio 自动加密。这简化了应用开发,把证书管理交给 Istio 统一处理。
完整 JWT + TLS gRPC 服务
// 完整的安全 gRPC 服务端
func main() {
// 1. TLS 配置
cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
tlsCreds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
})
// 2. 拦截器链(顺序很重要)
s := grpc.NewServer(
grpc.Creds(tlsCreds),
grpc.ChainUnaryInterceptor(
LoggingInterceptor, // 先记录所有请求
RecoveryInterceptor, // 防止 panic 崩服务
AuthInterceptor, // 认证(部分方法跳过)
),
)
// 3. 注册服务
pb.RegisterProductServiceServer(s, NewProductServer())
// 4. 启动
lis, _ := net.Listen("tcp", ":50051")
log.Println("Secure gRPC server on :50051")
s.Serve(lis)
}
安全红线:生产环境中,永远不要在公网使用 insecure.NewCredentials()。gRPC 没有加密传输时,Protobuf 二进制数据虽然可读性低,但仍然可以被截获和解码——因为 .proto 文件通常是公开的。至少要使用单向 TLS,微服务间推荐使用 mTLS。
本章小结:gRPC 安全分两层——TLS 保护传输,JWT/Token 保护调用权限。两层缺一不可:TLS 确保数据不被窃听,Token 认证确保调用者有权限执行操作。在 Kubernetes 环境中,优先考虑 Istio 统一管理 mTLS,减少应用层的证书管理复杂度。