gRPC 入門指南 — 自定義認證(六)

前言

在前面的章節 (文末推薦閱讀) 中,我們通過 TLS 證書的方式對通信數據進行了加密。另外,我們還可以給 RPC 方法添加自定義的驗證方法,使得數據更加安全。這篇文章我們就以 Token 認證爲例,介紹 gRPC 如何添加自定義驗證方法。

自定義認證

gRPC 官方默認提供了用於自定義認證的接口,作用是將所需的安全認證信息添加到 RPC 方法的上下中。

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

接下來我們自定義 token,實現這兩個方法:

type Token struct {
 AppId     string
 AppSecret string
}

// GetRequestMetadata 獲取當前請求認證所需的元數據
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 false
}

Server 端

完整的代碼如下:

package main

import (
 "context"
 pb "go-grpc-example/6-rpc_auth/proto"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/metadata"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

const (
 Address string = ":8000"
 Network string = "tcp"
)

type SimpleService struct{}

func (s *SimpleService) GetSimpleInfo(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
 // 檢測Token是否有效
 if err := check(ctx); err != nil {
  return nil, err
 }
 data := req.Data
 log.Printf("get from client: %v", data)

 resp := pb.SimpleResponse{
  Code:  1,
  Value: "gRPC",
 }
 return &resp, nil
}

func main() {

 listener, err := net.Listen(Network, Address)
 if err != nil {
  log.Fatalf("net.listen err: %v", err)
 }
 log.Println(Address, " net listening...")

 grpcServer := grpc.NewServer()

 pb.RegisterSimpleServer(grpcServer, &SimpleService{})

 err = grpcServer.Serve(listener)
 if err != nil {
  log.Fatalf("grpc server err: %v", err)
 }
}

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_auth" || appSecret != "123456" {
  return status.Errorf(codes.Unauthenticated, "Token無效,appId:%v,appSecret:%v", appId, appSecret)
 }
 return nil
}

上面的代碼,在 GetSimpleInfo() 方法中,在處理實際的業務邏輯之前,我們調用了 check() 函數驗證 token 信息是否正確。

Client 端

完整的代碼如下:

package main

import (
 "context"
 pb "go-grpc-example/5-security/proto"
 "google.golang.org/grpc"
 "log"
)

const Address = ":8000"

type Token struct {
 AppId     string
 AppSecret string
}

// GetRequestMetadata 獲取當前請求認證所需的元數據
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 false
}

func main() {

 // Token token認證
 token := Token{
  AppId:     "grpc_auth",
  AppSecret: "123456",
 }

 opts := []grpc.DialOption{
  grpc.WithInsecure(),
  grpc.WithPerRPCCredentials(&token),
 }

 conn, err := grpc.Dial(Address, opts...)
 if err != nil {
  log.Fatalf("dial conn err: %v", err)
 }
 defer conn.Close()

 grpcClient := pb.NewSimpleClient(conn)

 req := pb.SimpleRequest{
  Data: "seekload",
 }
 resp, err := grpcClient.GetSimpleInfo(context.Background()&req)
 if err != nil {
  log.Fatalf("resp err: %v", err)
 }
 log.Printf("get from client,code: %v,value: %v", resp.Code, resp.Value)

}

上面的代碼,自定義 token,實現了 gRPC 提供的兩個方法。在客戶端中調用 Dial() 時添加自定義驗證方法。

grpc.WithPerRPCCredentials(&token)

驗證

分別運行服務端和客戶端,輸出:

go run server.go
:8000  net listening...
get from client: seekload

go run client.go
get from client,code: 1,value: gRPC

可以製造 token 信息不正確,客戶端返回錯誤:

resp err: rpc error: code = Unauthenticated desc = Token無效,appId:grpc_auth,appSecret:123457

思考

大家試想下,實際業務中肯定不止一個 RPC 方法,每個方法中都需要手動加一段驗證 token 信息的代碼,這樣豈不是很繁瑣。那有沒有 “一處驗證,處處驗證” 的方法?答案肯定是有的,對!攔截器,這就是我們下一篇文章給大家介紹的。

對了,看完文章,記得點擊下方的卡片。關注我哦~ 👇👇👇

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