Go gRPC 之 TLS 認證 - 自定義方法認證

【導讀】本文將介紹如何爲 gRPC 添加安全機制,包括 TLS 證書認證和 Token 認證。

TLS 證書認證

什麼是 TLS

TLS(Transport Layer Security,安全傳輸層),TLS 是建立在傳輸層TCP 協議之上的協議,服務於應用層,它的前身是 SSL(Secure Socket Layer,安全套接字層),它實現了將應用層的報文進行加密後再交由 TCP 進行傳輸的功能。

TLS 的作用

TLS 協議主要解決如下三個網絡安全問題。

生成私鑰

生成 RSA 私鑰:openssl genrsa -out server.key 2048

生成 RSA 私鑰,命令的最後一個參數,將指定生成密鑰的位數,如果沒有指定,默認 512

生成 ECC 私鑰:openssl ecparam -genkey -name secp384r1 -out server.key

生成 ECC 私鑰,命令爲橢圓曲線密鑰參數生成及操作,本文中 ECC 曲線選擇的是 secp384r1

生成公鑰

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

openssl req:生成自簽名證書,-new 指生成證書請求、-sha256 指使用 sha256 加密、-key 指定私鑰文件、-x509 指輸出證書、-days 3650 爲有效期

此後則輸入證書擁有者信息

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
Organizational Unit Name (eg, section) []:Dev
Common Name (e.g. server FQDN or YOUR name) []:go-grpc-example
Email Address []:xxx@xxx.com

服務端構建 TLS 證書並認證

func main() {
 // 監聽本地端口
 listener, err := net.Listen(Network, Address)
 if err != nil {
  log.Fatalf("net.Listen err: %v", err)
 }
 // 從輸入證書文件和密鑰文件爲服務端構造TLS憑證
 creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem""../pkg/tls/server.key")
 if err != nil {
  log.Fatalf("Failed to generate credentials %v", err)
 }
 // 新建gRPC服務器實例,並開啓TLS認證
 grpcServer := grpc.NewServer(grpc.Creds(creds))
 // 在gRPC服務器註冊我們的服務
 pb.RegisterSimpleServer(grpcServer, &SimpleService{})
 log.Println(Address + " net.Listing whth TLS and token...")
 //用服務器 Serve() 方法以及我們的端口信息區實現阻塞等待,直到進程被殺死或者 Stop() 被調用
 err = grpcServer.Serve(listener)
 if err != nil {
  log.Fatalf("grpcServer.Serve err: %v", err)
 }
}

完整 server.go 代碼

客戶端配置 TLS 連接

var grpcClient pb.SimpleClient

func main() {
 //從輸入的證書文件中爲客戶端構造TLS憑證
 creds, err := credentials.NewClientTLSFromFile("../pkg/tls/server.pem""go-grpc-example")
 if err != nil {
  log.Fatalf("Failed to create TLS credentials %v", err)
 }
 // 連接服務器
 conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
 if err != nil {
  log.Fatalf("net.Connect err: %v", err)
 }
 defer conn.Close()

 // 建立gRPC連接
 grpcClient = pb.NewSimpleClient(conn)
}

完整 client.go 代碼

到這裏,已經完成 TLS 證書認證了,gRPC 傳輸不再是明文傳輸。此外,添加自定義的驗證方法能使 gRPC 相對更安全。下面以 Token 認證爲例,介紹 gRPC 如何添加自定義驗證方法。

Token 認證

客戶端發請求時,添加 Token 到上下文context.Context中,服務器接收到請求,先從上下文中獲取 Token 驗證,驗證通過才進行下一步處理。

客戶端請求添加 Token 到上下文中

type PerRPCCredentials interface {
    GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
    RequireTransportSecurity() bool
}

gRPC 中默認定義了 PerRPCCredentials,是提供用於自定義認證的接口,它的作用是將所需的安全認證信息添加到每個 RPC 方法的上下文中。其包含 2 個方法:

接下來我們實現這兩個方法

// Token token認證
type Token struct {
 AppID     string
 AppSecret string
}

// GetRequestMetadata 獲取當前請求認證所需的元數據(metadata)
func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
 return map[string]string{"app_id": t.AppID, "app_secret": t.AppSecret}, nil
}

// RequireTransportSecurity 是否需要基於 TLS 認證進行安全傳輸
func (t *Token) RequireTransportSecurity() bool {
 return true
}

然後再客戶端中調用 Dial 時添加自定義驗證方法進去

//構建Token
 token := auth.Token{
  AppID:     "grpc_token",
  AppSecret: "123456",
 }
 // 連接服務器
 conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))

完整 client.go 代碼

服務端驗證 Token

首先需要從上下文中獲取元數據,然後從元數據中解析 Token 進行驗證

// Check 驗證token
func Check(ctx context.Context) error {
 //從上下文中獲取元數據
 md, ok := metadata.FromIncomingContext(ctx)
 if !ok {
  return status.Errorf(codes.Unauthenticated, "獲取Token失敗")
 }
 var (
  appID     string
  appSecret string
 )
 if value, ok := md["app_id"]; ok {
  appID = value[0]
 }
 if value, ok := md["app_secret"]; ok {
  appSecret = value[0]
 }
 if appID != "grpc_token" || appSecret != "123456" {
  return status.Errorf(codes.Unauthenticated, "Token無效: app_id=%s, app_secret=%s", appID, appSecret)
 }
 return nil
}

// Route 實現Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
    //檢測Token是否有效
 if err := Check(ctx); err != nil {
  return nil, err
 }
 res := pb.SimpleResponse{
  Code:  200,
  Value: "hello " + req.Data,
 }
 return &res, nil
}

完整 server.go 代碼

服務端代碼中,每個服務的方法都需要添加 Check(ctx) 來驗證 Token,這樣十分麻煩。gRPC 攔截器,能很好地解決這個問題。gRPC 攔截器功能類似中間件,攔截器收到請求後,先進行一些操作,然後才進入服務的代碼處理。

服務端添加攔截器

func main() {
 // 監聽本地端口
 listener, err := net.Listen(Network, Address)
 if err != nil {
  log.Fatalf("net.Listen err: %v", err)
 }
 // 從輸入證書文件和密鑰文件爲服務端構造TLS憑證
 creds, err := credentials.NewServerTLSFromFile("../pkg/tls/server.pem""../pkg/tls/server.key")
 if err != nil {
  log.Fatalf("Failed to generate credentials %v", err)
 }
 //普通方法:一元攔截器(grpc.UnaryInterceptor)
 var interceptor grpc.UnaryServerInterceptor
 interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  //攔截普通方法請求,驗證Token
  err = Check(ctx)
  if err != nil {
   return
  }
  // 繼續處理請求
  return handler(ctx, req)
 }
 // 新建gRPC服務器實例,並開啓TLS認證和Token認證
 grpcServer := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(interceptor))
 // 在gRPC服務器註冊我們的服務
 pb.RegisterSimpleServer(grpcServer, &SimpleService{})
 log.Println(Address + " net.Listing whth TLS and token...")
 //用服務器 Serve() 方法以及我們的端口信息區實現阻塞等待,直到進程被殺死或者 Stop() 被調用
 err = grpcServer.Serve(listener)
 if err != nil {
  log.Fatalf("grpcServer.Serve err: %v", err)
 }
}

客戶端發起請求,當 Token 不正確時候,會返回

Call Route err: rpc error: code = Unauthenticated desc = Token無效: app_id=grpc_token, app_secret=12345

總結

本篇介紹如何爲 gRPC 添加 TLS 證書認證和自定義認證,從而讓 gRPC 更安全。添加 gRPC 攔截器,從而省略在每個方法前添加 Token 檢測代碼,使代碼更簡潔。

教程源碼地址:https://github.com/Bingjian-Zhu/go-grpc-example

轉自:

cnblogs.com/FireworksEasyCool/p/12710325.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/oFZL1uVfKzyTA2IzryQIKg