Skip to content

gRPC 安全与 TLS

gRPC 默认要求 TLS 加密传输——这不是可选项,而是框架层面的安全设计。本篇深入 gRPC 的传输安全机制,从 TLS 基础概念、单向 TLS、mTLS 双向认证,到基于 metadata 的 Token/JWT 认证和拦截器授权,最终构建一个完整的安全 gRPC 服务。

一、gRPC 传输安全概述

gRPC 的安全模型分为两层:

┌──────────────────────────────────────────────────┐
│  应用层安全(认证 & 授权)                          │
│  - Token / JWT 认证                                │
│  - OAuth2                                          │
│  - RBAC / ABAC 授权                                │
│  实现方式:metadata + 拦截器                       │
├──────────────────────────────────────────────────┤
│  传输层安全(TLS)                                  │
│  - 单向 TLS:客户端验证服务端身份                   │
│  - 双向 TLS(mTLS):双方互相验证                   │
│  实现方式:credentials 包                          │
└──────────────────────────────────────────────────┘

为什么 gRPC 强制 TLS

  • 防止中间人攻击(MITM),保护传输中的数据。
  • 内部服务间通信也不应裸奔——零信任网络模型要求所有通信加密。
  • gRPC-Go 中如果不用 TLS,必须显式传入 insecure.NewCredentials(),这种「显式不安全」的设计迫使开发者意识到风险。

二、TLS 基础:证书、密钥、CA

1. 核心概念

概念说明
私钥(key)用于解密和签名,必须保密
公钥(cert)用于加密和验签,可公开
证书(cert)公钥 + 身份信息 + CA 签名,证明公钥属于某个主体
CA证书颁发机构,用自身私钥签发证书
CSR证书签名请求,包含公钥和身份信息,提交给 CA 签名
证书链证书可能由中间 CA 签发,需要追溯到根 CA

2. TLS 握手流程

单向 TLS:
客户端                          服务端
  | --- ClientHello (支持的TLS版本、密码套件) ---> |
  | <-- ServerHello + 证书 + 公钥 --------------- |
  | --- 验证证书 -> 生成对称密钥 -> 加密发送 -----> |
  | <-- 用对称密钥加密通信 ----------------------- |

双向 TLS (mTLS):
客户端                          服务端
  | --- ClientHello ----------------------------> |
  | <-- ServerHello + 服务端证书 ----------------- |
  | --- 客户端证书 + 用服务端公钥加密的密钥 -----> |
  | <-- 验证客户端证书 -> 确认通信 --------------- |

三、自签名证书生成

在开发和内部服务间通信中,自签名 CA + 证书是常见做法。下面用 Go 代码生成完整的证书体系。

go
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/big"
	"os"
	"time"
)

// 生成自签名 CA 证书
func generateCA() (*x509.Certificate, interface{}, error) {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, err
	}

	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject: pkix.Name{
			Organization: []string{"Example CA"},
			CommonName:   "Example Root CA",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(10 * 365 * 24 * time.Hour),
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
		MaxPathLen:            1,
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
	if err != nil {
		return nil, nil, err
	}

	cert, err := x509.ParseCertificate(certDER)
	if err != nil {
		return nil, nil, err
	}

	return cert, priv, nil
}

// 用 CA 签发服务端/客户端证书
func generateCert(caCert *x509.Certificate, caPriv interface{},
	commonName string, isServer bool,
) (*x509.Certificate, interface{}, error) {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, nil, err
	}

	template := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().Unix()),
		Subject: pkix.Name{
			Organization: []string{"Example"},
			CommonName:   commonName,
		},
		NotBefore:   time.Now(),
		NotAfter:    time.Now().Add(365 * 24 * time.Hour),
		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage: []x509.ExtKeyUsage{},
	}
	if isServer {
		template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
		template.DNSNames = []string{"localhost", "example.com"}
		template.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
	} else {
		template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &priv.PublicKey, caPriv)
	if err != nil {
		return nil, nil, err
	}

	cert, err := x509.ParseCertificate(certDER)
	if err != nil {
		return nil, nil, err
	}

	return cert, priv, nil
}

// 保存证书和私钥到 PEM 文件
func saveCertPEM(filename string, cert *x509.Certificate) error {
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
}

func saveKeyPEM(filename string, priv interface{}) error {
	keyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return err
	}
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()
	return pem.Encode(f, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})
}

func main() {
	// 1. 生成 CA
	caCert, caPriv, err := generateCA()
	if err != nil {
		fmt.Printf("generate CA: %v\n", err)
		return
	}
	fmt.Printf("CA cert: CN=%s, valid until %s\n",
		caCert.Subject.CommonName, caCert.NotAfter.Format("2006-01-02"))

	// 2. 用 CA 签发服务端证书
	serverCert, serverKey, err := generateCert(caCert, caPriv, "server.example.com", true)
	if err != nil {
		fmt.Printf("generate server cert: %v\n", err)
		return
	}
	fmt.Printf("Server cert: CN=%s, SAN=%v\n",
		serverCert.Subject.CommonName, serverCert.DNSNames)

	// 3. 用 CA 签发客户端证书(用于 mTLS)
	clientCert, clientKey, err := generateCert(caCert, caPriv, "client.example.com", false)
	if err != nil {
		fmt.Printf("generate client cert: %v\n", err)
		return
	}
	fmt.Printf("Client cert: CN=%s, ExtKeyUsage=%v\n",
		clientCert.Subject.CommonName, clientCert.ExtKeyUsage)

	// 4. 保存到文件(实际项目中保存到 certs/ 目录)
	_ = saveCertPEM("ca.crt", caCert)
	_ = saveKeyPEM("ca.key", caPriv)
	_ = saveCertPEM("server.crt", serverCert)
	_ = saveKeyPEM("server.key", serverKey)
	_ = saveCertPEM("client.crt", clientCert)
	_ = saveKeyPEM("client.key", clientKey)

	fmt.Println("\nAll certificates generated successfully")
	fmt.Println("Files: ca.crt, ca.key, server.crt, server.key, client.crt, client.key")
}

也可以用 OpenSSL 命令行生成:

bash
# 生成 CA 私钥和证书
openssl genrsa -out ca.key 2048
openssl req -new -x509 -key ca.key -out ca.crt -days 3650 -subj "/CN=Example CA"

# 生成服务端证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365

# 生成客户端证书(mTLS 用)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365

四、服务端 TLS 配置

go
package main

import (
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// 服务端单向 TLS:客户端验证服务端身份
func newTLSServer(addr string) {
	// 方式一:从文件加载证书(最简单)
	// creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
	// if err != nil {
	//     log.Fatalf("load TLS: %v", err)
	// }

	// 方式二:使用 tls.Config(更灵活)
	// cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
	// tlsConfig := &tls.Config{
	//     Certificates: []tls.Certificate{cert},
	//     MinVersion:   tls.VersionTLS12, // 强制 TLS 1.2+
	// }
	// creds := credentials.NewTLS(tlsConfig)

	// 完整服务端启动
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("listen: %v", err)
	}

	// server := grpc.NewServer(grpc.Creds(creds))
	// userv1.RegisterUserServiceServer(server, &userServiceImpl{})
	// log.Println(server.Serve(lis))

	fmt.Printf("TLS server would listen on %s\n", addr)
}

func main() {
	// 演示 credentials 包的用法
	// 实际运行需要证书文件
	fmt.Println("Server TLS configuration:")
	fmt.Println("  credentials.NewServerTLSFromFile(certFile, keyFile)")
	fmt.Println("  credentials.NewTLS(tlsConfig)")

	// 用 nil tls.Config 演示选项组装(实际使用需真实配置)
	server := grpc.NewServer(
		grpc.Creds(credentials.NewTLS(nil)),
	)
	defer server.Stop()
	fmt.Printf("server created: %T\n", server)

	newTLSServer(":50051")
}

五、客户端 TLS 配置

go
package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// 客户端单向 TLS:验证服务端证书
func newTLSClient(addr string) {
	// 方式一:信任系统证书 + 指定 serverName
	// creds, err := credentials.NewClientTLSFromFile("server.crt", "example.com")
	// if err != nil {
	//     log.Fatalf("load TLS: %v", err)
	// }
	// conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))

	// 方式二:自定义 CA 池(用于自签名证书)
	certPool := x509.NewCertPool()
	// caCert, _ := os.ReadFile("ca.crt")
	// certPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		RootCAs:    certPool,              // 信任的 CA 列表
		ServerName: "example.com",         // 必须与证书 CN/SAN 匹配
		MinVersion: tls.VersionTLS12,
	}
	creds := credentials.NewTLS(tlsConfig)

	conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	fmt.Printf("TLS client connected to %s\n", addr)
}

// 开发环境:跳过证书验证(仅用于测试,绝不用于生产)
func newInsecureTLSClient(addr string) {
	tlsConfig := &tls.Config{
		InsecureSkipVerify: true, // 危险!跳过证书验证
	}
	creds := credentials.NewTLS(tlsConfig)
	conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
	defer conn.Close()
	fmt.Printf("Insecure TLS client (testing only) -> %s\n", addr)
}

func main() {
	// 演示 CA 池加载
	certPool := x509.NewCertPool()
	fmt.Printf("Empty cert pool created: %v\n", certPool)

	// 实际加载 CA 证书
	// if caCert, err := os.ReadFile("ca.crt"); err == nil {
	//     if certPool.AppendCertsFromPEM(caCert) {
	//         fmt.Println("CA cert added to pool")
	//     }
	// }
	_ = os.ReadFile

	newTLSClient("example.com:443")
}

六、双向 TLS(mTLS)

mTLS 要求客户端也出示证书,服务端验证客户端身份。这是内部服务间最高安全级别的认证方式。

go
package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// === mTLS 服务端配置 ===
func newMTLSServer(addr string) {
	// 加载服务端证书和私钥
	// serverCert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	// if err != nil {
	//     log.Fatalf("load server cert: %v", err)
	// }

	// 加载 CA 证书用于验证客户端
	certPool := x509.NewCertPool()
	// caCert, _ := os.ReadFile("ca.crt")
	// certPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{}, // serverCert,
		ClientAuth:   tls.RequireAndVerifyClientCert, // 强制要求并验证客户端证书
		ClientCAs:    certPool,                        // 信任的 CA
		MinVersion:   tls.VersionTLS12,
	}

	// ClientAuth 选项说明:
	// tls.NoClientCert                  - 不要求客户端证书
	// tls.RequestClientCert             - 请求但不要求
	// tls.RequireAnyClientCert          - 要求但不验证
	// tls.VerifyClientCertIfGiven       - 如果提供了则验证
	// tls.RequireAndVerifyClientCert    - 强制要求并验证(mTLS 用这个)

	creds := credentials.NewTLS(tlsConfig)

	lis, _ := net.Listen("tcp", addr)
	server := grpc.NewServer(grpc.Creds(creds))
	// 注册服务...
	_ = server
	_ = lis
	fmt.Printf("mTLS server on %s (requires client cert)\n", addr)
}

// === mTLS 客户端配置 ===
func newMTLSClient(addr string) {
	// 加载客户端证书和私钥
	// clientCert, err := tls.LoadX509KeyPair("client.crt", "client.key")
	// if err != nil {
	//     log.Fatalf("load client cert: %v", err)
	// }

	// 加载 CA 用于验证服务端
	certPool := x509.NewCertPool()
	// caCert, _ := os.ReadFile("ca.crt")
	// certPool.AppendCertsFromPEM(caCert)

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{}, // clientCert, // 客户端证书
		RootCAs:      certPool,             // 信任的 CA
		ServerName:   "example.com",
		MinVersion:   tls.VersionTLS12,
	}

	creds := credentials.NewTLS(tlsConfig)
	conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	fmt.Printf("mTLS client -> %s (with client cert)\n", addr)
}

func main() {
	// 演示 mTLS 的 TLS 配置结构
	fmt.Println("=== mTLS Server Config ===")
	fmt.Println("  Certificates: [server cert]")
	fmt.Println("  ClientAuth: RequireAndVerifyClientCert")
	fmt.Println("  ClientCAs: [trusted CA pool]")
	fmt.Println()
	fmt.Println("=== mTLS Client Config ===")
	fmt.Println("  Certificates: [client cert]")
	fmt.Println("  RootCAs: [trusted CA pool]")
	fmt.Println("  ServerName: must match server cert CN/SAN")

	newMTLSServer(":50051")
	newMTLSClient("127.0.0.1:50051")
}

七、Token 认证:基于 metadata

TLS 解决了传输加密和身份验证,但应用层还需要认证「谁在调用」。gRPC 通过 metadata 传递认证信息。

go
package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// === Token 管理器 ===
type TokenManager struct {
	mu     sync.RWMutex
	tokens map[string]tokenInfo // token -> user info
}

type tokenInfo struct {
	UserID    string
	Role      string
	ExpiresAt time.Time
}

func NewTokenManager() *TokenManager {
	return &TokenManager{tokens: make(map[string]tokenInfo)}
}

// 生成 token
func (tm *TokenManager) Issue(userID, role string) string {
	token := fmt.Sprintf("token-%s-%d", userID, time.Now().UnixNano())
	tm.mu.Lock()
	defer tm.mu.Unlock()
	tm.tokens[token] = tokenInfo{
		UserID:    userID,
		Role:      role,
		ExpiresAt: time.Now().Add(time.Hour),
	}
	return token
}

// 验证 token
func (tm *TokenManager) Validate(token string) (*tokenInfo, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()
	info, ok := tm.tokens[token]
	if !ok {
		return nil, status.Error(codes.Unauthenticated, "invalid token")
	}
	if time.Now().After(info.ExpiresAt) {
		return nil, status.Error(codes.Unauthenticated, "token expired")
	}
	return &info, nil
}

// === 客户端:注入 token 到 metadata ===
func withToken(ctx context.Context, token string) context.Context {
	return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
}

// 客户端 token 自动注入拦截器
func tokenInjectInterceptor(token string) grpc.UnaryClientInterceptor {
	return func(ctx context.Context, method string, req, reply interface{},
		cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption,
	) error {
		ctx = withToken(ctx, token)
		return invoker(ctx, method, req, reply, cc, opts...)
	}
}

func main() {
	tm := NewTokenManager()

	// 1. 用户登录获取 token
	token := tm.Issue("user-42", "admin")
	fmt.Printf("issued token: %s\n", token)

	// 2. 模拟客户端调用时注入 token
	ctx := withToken(context.Background(), token)
	md, _ := metadata.FromOutgoingContext(ctx)
	fmt.Printf("outgoing metadata: %v\n", md)

	// 3. 服务端验证 token
	// 模拟服务端从 incoming metadata 提取 token
	incomingMD := metadata.NewIncomingContext(context.Background(),
		metadata.Pairs("authorization", "Bearer "+token))
	extractedToken := ""
	if vals := incomingMD.Get("authorization"); len(vals) > 0 {
		extractedToken = vals[0]
	}
	fmt.Printf("extracted: %s\n", extractedToken)

	info, err := tm.Validate(extractedToken)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("validated: userID=%s role=%s\n", info.UserID, info.Role)

	// 4. 演示过期 token
	expiredToken := tm.Issue("user-expired", "user")
	// 手动过期
	tm.mu.Lock()
	t := tm.tokens[expiredToken]
	t.ExpiresAt = time.Now().Add(-time.Hour)
	tm.tokens[expiredToken] = t
	tm.mu.Unlock()

	_, err = tm.Validate(expiredToken)
	fmt.Printf("expired token validation: %v\n", err)
}

八、OAuth2 与 JWT

1. JWT 认证

go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// 模拟 JWT(实际项目用 github.com/golang-jwt/jwt/v5)
// JWT 结构: header.payload.signature,均 base64url 编码

type JWTClaims struct {
	UserID string `json:"user_id"`
	Role   string `json:"role"`
	Exp    int64  `json:"exp"`
	Iat    int64  `json:"iat"`
}

// 模拟 JWT 签发(简化版,实际用 HMAC/RSA 签名)
func signJWT(claims JWTClaims, secret string) string {
	// 实际实现:
	// header := base64url(headerJSON)
	// payload := base64url(claimsJSON)
	// signature := HMAC-SHA256(header + "." + payload, secret)
	// return header + "." + payload + "." + signature
	return fmt.Sprintf("jwt.%s.%s.%s",
		claims.UserID, claims.Role, "fake-signature")
}

// 模拟 JWT 验证
func verifyJWT(token string, secret string) (*JWTClaims, error) {
	if token == "" {
		return nil, status.Error(codes.Unauthenticated, "empty token")
	}
	parts := strings.Split(token, ".")
	if len(parts) != 4 || parts[0] != "jwt" {
		return nil, status.Error(codes.Unauthenticated, "malformed token")
	}
	// 实际验证签名...
	claims := &JWTClaims{
		UserID: parts[1],
		Role:   parts[2],
		Exp:    time.Now().Add(time.Hour).Unix(),
	}
	if claims.Exp < time.Now().Unix() {
		return nil, status.Error(codes.Unauthenticated, "token expired")
	}
	return claims, nil
}

// JWT 认证拦截器
func jwtAuthInterceptor(secret string, whitelist map[string]bool) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{},
		info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
	) (interface{}, error) {
		// 白名单跳过
		if whitelist[info.FullMethod] {
			return handler(ctx, req)
		}

		// 从 metadata 提取 token
		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "no metadata")
		}
		values := md.Get("authorization")
		if len(values) == 0 {
			return nil, status.Error(codes.Unauthenticated, "no authorization")
		}

		token := strings.TrimPrefix(values[0], "Bearer ")
		claims, err := verifyJWT(token, secret)
		if err != nil {
			return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
		}

		// 将 claims 存入 context
		ctx = context.WithValue(ctx, jwtClaimsKey{}, claims)
		return handler(ctx, req)
	}
}

type jwtClaimsKey struct{}

func ClaimsFromContext(ctx context.Context) (*JWTClaims, bool) {
	c, ok := ctx.Value(jwtClaimsKey{}).(*JWTClaims)
	return c, ok
}

func main() {
	secret := "my-secret-key"

	// 签发 JWT
	token := signJWT(JWTClaims{
		UserID: "user-42",
		Role:   "admin",
		Exp:    time.Now().Add(time.Hour).Unix(),
		Iat:    time.Now().Unix(),
	}, secret)
	fmt.Printf("JWT token: %s\n", token)

	// 验证 JWT
	claims, err := verifyJWT(token, secret)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("claims: %+v\n", claims)

	// 模拟拦截器调用
	whitelist := map[string]bool{
		"/auth.AuthService/Login": true,
	}
	interceptor := jwtAuthInterceptor(secret, whitelist)

	// 测试白名单方法
	_, err = interceptor(
		context.Background(), "req",
		&grpc.UnaryServerInfo{FullMethod: "/auth.AuthService/Login"},
		func(ctx context.Context, req interface{}) (interface{}, error) {
			return "login ok", nil
		},
	)
	fmt.Printf("whitelist call: err=%v\n", err)

	// 测试需要认证的方法(带 token)
	ctx := metadata.NewIncomingContext(context.Background(),
		metadata.Pairs("authorization", "Bearer "+token))
	_, err = interceptor(ctx, "req",
		&grpc.UnaryServerInfo{FullMethod: "/shop.OrderService/GetOrder"},
		func(ctx context.Context, req interface{}) (interface{}, error) {
			c, _ := ClaimsFromContext(ctx)
			return fmt.Sprintf("order for %s", c.UserID), nil
		},
	)
	fmt.Printf("authenticated call: err=%v\n", err)

	// 测试无 token 的调用
	_, err = interceptor(context.Background(), "req",
		&grpc.UnaryServerInfo{FullMethod: "/shop.OrderService/GetOrder"},
		func(ctx context.Context, req interface{}) (interface{}, error) { return nil, nil },
	)
	fmt.Printf("unauthenticated call: err=%v\n", err)
}

2. OAuth2 Token

go
package main

import (
	"context"
	"fmt"
	"time"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/oauth"
)

// OAuth2 客户端凭证模式(适合服务间调用)
func newOAuth2Client(addr string) {
	// 配置 OAuth2 客户端凭证
	config := &clientcredentials.Config{
		ClientID:     "my-client-id",
		ClientSecret: "my-client-secret",
		TokenURL:     "https://auth.example.com/oauth2/token",
		Scopes:       []string{"order.read", "order.write"},
	}

	// 方式一:使用 gRPC 的 oauth 包
	// creds := oauth.NewTokenSource(config.TokenSource(context.Background()))
	// conn, _ := grpc.NewClient(addr,
	//     grpc.WithTransportCredentials(credentials.NewTLS(nil)), // TLS 传输层
	//     grpc.WithPerRPCCredentials(creds),                      // OAuth2 应用层
	// )

	// 方式二:使用 PerRPCCredentials 接口自定义
	tokenSource := config.TokenSource(context.Background())

	// 模拟获取 token
	_ = tokenSource

	fmt.Println("OAuth2 client configured:")
	fmt.Println("  ClientID:", config.ClientID)
	fmt.Println("  TokenURL:", config.TokenURL)
	fmt.Println("  Scopes:", config.Scopes)

	_ = grpc.NewClient
	_ = credentials.NewTLS
	_ = oauth.NewTokenSource
	_ = time.Second
}

九、拦截器实现认证与授权

go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// === 认证拦截器:验证身份 ===
type AuthContext struct {
	UserID string
	Role   string
	Scopes []string
}

type authCtxKey struct{}

func AuthInterceptor(secret string) grpc.UnaryServerInterceptor {
	whitelist := map[string]bool{
		"/auth.AuthService/Login":    true,
		"/auth.AuthService/Register": true,
	}

	return func(ctx context.Context, req interface{},
		info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
	) (interface{}, error) {
		if whitelist[info.FullMethod] {
			return handler(ctx, req)
		}

		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "no metadata")
		}

		tokens := md.Get("authorization")
		if len(tokens) == 0 {
			return nil, status.Error(codes.Unauthenticated, "missing authorization")
		}

		token := strings.TrimPrefix(tokens[0], "Bearer ")
		authCtx, err := authenticate(token, secret)
		if err != nil {
			return nil, status.Errorf(codes.Unauthenticated, "auth failed: %v", err)
		}

		ctx = context.WithValue(ctx, authCtxKey{}, authCtx)
		return handler(ctx, req)
	}
}

func authenticate(token string, secret string) (*AuthContext, error) {
	// 简化:实际验证 JWT 签名
	if token == "" {
		return nil, fmt.Errorf("empty token")
	}
	// 模拟解析
	if strings.Contains(token, "invalid") {
		return nil, fmt.Errorf("invalid signature")
	}
	return &AuthContext{
		UserID: "user-42",
		Role:   "user",
		Scopes: []string{"order:read", "order:write"},
	}, nil
}

func GetAuthContext(ctx context.Context) (*AuthContext, bool) {
	ac, ok := ctx.Value(authCtxKey{}).(*AuthContext)
	return ac, ok
}

// === 授权拦截器:检查权限 ===
// 权限矩阵:方法 -> 允许的角色
var methodRoles = map[string][]string{
	"/shop.OrderService/GetOrder":    {"user", "admin"},
	"/shop.OrderService/CreateOrder": {"user", "admin"},
	"/shop.OrderService/DeleteOrder": {"admin"}, // 仅管理员
	"/shop.OrderService/ListAllOrders": {"admin"}, // 仅管理员
}

func AuthzInterceptor(ctx context.Context, req interface{},
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
	allowedRoles, requiresAuthz := methodRoles[info.FullMethod]
	if !requiresAuthz {
		// 不在权限矩阵中的方法,默认允许
		return handler(ctx, req)
	}

	authCtx, ok := GetAuthContext(ctx)
	if !ok {
		return nil, status.Error(codes.Unauthenticated, "no auth context")
	}

	// 检查角色
	hasRole := false
	for _, role := range allowedRoles {
		if authCtx.Role == role {
			hasRole = true
			break
		}
	}
	if !hasRole {
		return nil, status.Errorf(codes.PermissionDenied,
			"role %q is not allowed for %s (requires: %v)",
			authCtx.Role, info.FullMethod, allowedRoles)
	}

	return handler(ctx, req)
}

// === Scope 级别授权 ===
func requireScope(requiredScope string) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{},
		info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
	) (interface{}, error) {
		authCtx, ok := GetAuthContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "no auth context")
		}

		hasScope := false
		for _, s := range authCtx.Scopes {
			if s == requiredScope {
				hasScope = true
				break
			}
		}
		if !hasScope {
			return nil, status.Errorf(codes.PermissionDenied,
				"missing required scope: %s", requiredScope)
		}
		return handler(ctx, req)
	}
}

func main() {
	secret := "jwt-secret"

	// 组装拦截器链
	server := grpc.NewServer(grpc.ChainUnaryInterceptor(
		AuthInterceptor(secret),         // 1. 认证
		AuthzInterceptor,                // 2. 授权(角色检查)
		requireScope("order:read"),      // 3. 授权(scope 检查)
	))
	fmt.Printf("server with auth + authz: %T\n", server)

	// 测试:模拟一个 admin 用户访问 DeleteOrder
	adminToken := "admin-token"
	ctx := metadata.NewIncomingContext(context.Background(),
		metadata.Pairs("authorization", "Bearer "+adminToken))

	// 修改 authenticate 让 admin-token 返回 admin 角色
	// (简化演示,直接手动构造)
	authCtx := &AuthContext{UserID: "admin-1", Role: "admin", Scopes: []string{"order:read", "order:write"}}
	ctx = context.WithValue(ctx, authCtxKey{}, authCtx)

	// 测试角色检查
	allowedRoles := methodRoles["/shop.OrderService/DeleteOrder"] // ["admin"]
	fmt.Printf("DeleteOrder allowed roles: %v, user role: %s\n", allowedRoles, authCtx.Role)

	// 测试普通用户访问管理员方法
	userAuthCtx := &AuthContext{UserID: "user-1", Role: "user", Scopes: []string{"order:read"}}
	userHasRole := false
	for _, r := range allowedRoles {
		if userAuthCtx.Role == r {
			userHasRole = true
		}
	}
	fmt.Printf("user accessing admin method: allowed=%v\n", userHasRole)

	// 测试 scope 检查
	requiredScope := "order:write"
	userHasScope := false
	for _, s := range userAuthCtx.Scopes {
		if s == requiredScope {
			userHasScope = true
		}
	}
	fmt.Printf("user has scope %q: %v (scopes: %v)\n", requiredScope, userHasScope, userAuthCtx.Scopes)

	_ = time.Second
	_ = log.Printf
}

十、完整示例:带认证和 TLS 的安全 gRPC 服务

go
package main

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"log"
	"math/big"
	"net"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// === 1. 内存证书生成(用于演示,无需文件) ===
type certPair struct {
	certPEM []byte
	keyPEM  []byte
	cert    *x509.Certificate
	key     interface{}
}

func generateCertInMemory(commonName string, isCA bool, isServer bool) *certPair {
	priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

	template := &x509.Certificate{
		SerialNumber: big.NewInt(time.Now().UnixNano()),
		Subject:      pkix.Name{CommonName: commonName, Organization: []string{"Demo"}},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(24 * time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	if isCA {
		template.KeyUsage |= x509.KeyUsageCertSign
		template.BasicConstraintsValid = true
		template.IsCA = true
	}
	if isServer {
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
		template.DNSNames = []string{"localhost"}
	} else if !isCA {
		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
	}

	var parent *x509.Certificate
	var signer interface{} = priv
	// 自签名
	parent = template

	certDER, _ := x509.CreateCertificate(rand.Reader, template, parent, &priv.PublicKey, signer)
	cert, _ := x509.ParseCertificate(certDER)

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
	keyBytes, _ := x509.MarshalPKCS8PrivateKey(priv)
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})

	return &certPair{certPEM: certPEM, keyPEM: keyPEM, cert: cert, key: priv}
}

// === 2. 用户存储 ===
type User struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Role  string `json:"role"`
}

type UserStore struct {
	mu     sync.RWMutex
	users  map[string]*User
	tokens map[string]string // token -> userID
}

func NewUserStore() *UserStore {
	return &UserStore{
		users: map[string]*User{
			"u1": {ID: "u1", Name: "alice", Role: "admin"},
			"u2": {ID: "u2", Name: "bob", Role: "user"},
		},
		tokens: make(map[string]string),
	}
}

func (s *UserStore) Login(userID string) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.users[userID]; !ok {
		return "", fmt.Errorf("user not found")
	}
	token := fmt.Sprintf("tok-%s-%d", userID, time.Now().UnixNano())
	s.tokens[token] = userID
	return token, nil
}

func (s *UserStore) ValidateToken(token string) (*User, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	userID, ok := s.tokens[token]
	if !ok {
		return nil, fmt.Errorf("invalid token")
	}
	user, ok := s.users[userID]
	if !ok {
		return nil, fmt.Errorf("user not found")
	}
	return user, nil
}

func (s *UserStore) Get(userID string) (*User, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	user, ok := s.users[userID]
	if !ok {
		return nil, fmt.Errorf("user %s not found", userID)
	}
	return user, nil
}

// === 3. 认证拦截器 ===
type authCtxKey struct{}

func AuthInterceptor(store *UserStore) grpc.UnaryServerInterceptor {
	whitelist := map[string]bool{
		"/auth.AuthService/Login": true,
	}
	return func(ctx context.Context, req interface{},
		info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
	) (interface{}, error) {
		if whitelist[info.FullMethod] {
			return handler(ctx, req)
		}
		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "no metadata")
		}
		tokens := md.Get("authorization")
		if len(tokens) == 0 {
			return nil, status.Error(codes.Unauthenticated, "missing token")
		}
		token := strings.TrimPrefix(tokens[0], "Bearer ")
		user, err := store.ValidateToken(token)
		if err != nil {
			return nil, status.Error(codes.Unauthenticated, err.Error())
		}
		ctx = context.WithValue(ctx, authCtxKey{}, user)
		return handler(ctx, req)
	}
}

// === 4. 授权拦截器 ===
func AuthzInterceptor(ctx context.Context, req interface{},
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
	// 仅管理员可访问的方法
	adminOnly := map[string]bool{
		"/user.UserService/DeleteUser": true,
	}
	if !adminOnly[info.FullMethod] {
		return handler(ctx, req)
	}

	user, ok := ctx.Value(authCtxKey{}).(*User)
	if !ok {
		return nil, status.Error(codes.Unauthenticated, "no auth context")
	}
	if user.Role != "admin" {
		return nil, status.Errorf(codes.PermissionDenied,
			"admin role required, you are %s", user.Role)
	}
	return handler(ctx, req)
}

// === 5. 构建 TLS 配置 ===
func buildServerTLSConfig(cp *certPair) *tls.Config {
	cert, _ := tls.X509KeyPair(cp.certPEM, cp.keyPEM)
	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		MinVersion:   tls.VersionTLS12,
	}
}

func buildClientTLSConfig(cp *certPair) *tls.Config {
	certPool := x509.NewCertPool()
	certPool.AddCert(cp.cert)
	return &tls.Config{
		RootCAs:    certPool,
		ServerName: "localhost",
		MinVersion: tls.VersionTLS12,
	}
}

// === 6. 服务端 ===
func runSecureServer(addr string, store *UserStore, serverCert *certPair) (*grpc.Server, net.Listener) {
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("listen: %v", err)
	}

	creds := credentials.NewTLS(buildServerTLSConfig(serverCert))
	server := grpc.NewServer(
		grpc.Creds(creds),
		grpc.ChainUnaryInterceptor(
			AuthInterceptor(store),
			AuthzInterceptor,
		),
	)

	go func() {
		log.Printf("secure gRPC server on %s (TLS + Auth)", lis.Addr())
		if err := server.Serve(lis); err != nil {
			log.Printf("serve: %v", err)
		}
	}()
	return server, lis
}

// === 7. 客户端 ===
func runSecureClient(addr string, caCert *certPair, store *UserStore) {
	creds := credentials.NewTLS(buildClientTLSConfig(caCert))
	conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Printf("dial: %v", err)
		return
	}
	defer conn.Close()

	// 模拟客户端调用流程
	// 1. 登录获取 token
	token, err := store.Login("u1")
	if err != nil {
		log.Printf("login: %v", err)
		return
	}
	fmt.Printf("client login: token=%s\n", token)

	// 2. 带 token 调用(通过 metadata 注入)
	ctx := metadata.AppendToOutgoingContext(
		context.Background(), "authorization", "Bearer "+token)

	// 模拟一次 RPC 调用(实际用 protoc 生成的 client)
	// resp, err := userv1.NewUserServiceClient(conn).GetUser(ctx, &userv1.GetUserRequest{Id: "u1"})
	user, err := store.Get("u1")
	if err != nil {
		log.Printf("get user: %v", err)
		return
	}
	fmt.Printf("client got user: %+v\n", user)
	_ = ctx
}

// === main ===
func main() {
	// 1. 生成证书
	fmt.Println("=== Generating certificates ===")
	caCert := generateCertInMemory("Demo CA", true, false)
	serverCert := generateCertInMemory("localhost", false, true)
	fmt.Printf("CA: CN=%s\n", caCert.cert.Subject.CommonName)
	fmt.Printf("Server: CN=%s, SAN=%v\n", serverCert.cert.Subject.CommonName, serverCert.cert.DNSNames)

	// 2. 初始化存储
	store := NewUserStore()

	// 3. 启动安全服务端
	fmt.Println("\n=== Starting secure server ===")
	server, lis := runSecureServer(":50055", store, serverCert)

	// 4. 客户端调用
	fmt.Println("\n=== Client calling ===")
	runSecureClient("127.0.0.1:50055", caCert, store)

	// 5. 等待退出
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	fmt.Println("\n=== Shutting down ===")
	server.GracefulStop()
	_ = lis.Close()
	fmt.Println("stopped")
}

这个完整示例演示了一个安全 gRPC 服务的全貌:内存生成自签名证书 + TLS 传输加密 + Token 认证拦截器 + 角色授权拦截器。实际生产中只需将内存证书替换为从文件/Secret 加载的正式证书,将内存 token 替换为 JWT/OAuth2 即可。

十一、小结

本篇深入 gRPC 安全与 TLS:

  • 传输安全概述:gRPC 安全分两层——传输层 TLS 和应用层认证/授权。
  • TLS 基础:私钥、公钥、证书、CA、CSR 的概念,TLS 握手流程。
  • 自签名证书:用 Go crypto/x509 包或 OpenSSL 生成 CA + 服务端/客户端证书。
  • 服务端 TLScredentials.NewServerTLSFromFileNewTLS(tls.Config) 配置证书。
  • 客户端 TLS:信任 CA 池 + ServerName 校验,开发环境可跳过验证(绝不用于生产)。
  • mTLS 双向认证:服务端 ClientAuth: RequireAndVerifyClientCert,客户端提供 Certificates,是内部服务最高安全级别。
  • Token 认证:通过 metadata 传递 authorization: Bearer <token>,拦截器提取并验证。
  • JWT/OAuth2:JWT 拦截器验证签名和过期时间,OAuth2 用 PerRPCCredentials 自动注入 token。
  • 认证拦截器:白名单跳过、token 验证、claims 注入 context。
  • 授权拦截器:角色检查(RBAC)和 scope 检查,PermissionDenied 状态码拒绝。
  • 完整示例:内存证书 + TLS + Token 认证 + 角色授权的端到端安全服务。

gRPC 系列教程到此结束。从 Protobuf 编码原理到服务端/客户端深度实现,从拦截器中间件到错误处理,从 REST 集成到安全 TLS,你已经具备了构建生产级 gRPC 系统的完整知识体系。