Chapter 08

认证与安全

从 TLS 加密传输到 mTLS 双向认证,构建生产级 gRPC 服务的安全防线

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,减少应用层的证书管理复杂度。