gRPC 除了提供 TLS 认证之外,还提供自定义认证方式。

在 gRPC 中定义了 PerRPCCredentials 接口,它的定义如下,它是 gRPC 提供的用于实现自定义认证的接口,**它的作用是将所需的安全认证信息添加到每一次 RPC 调用的 Context 中。

注意: PerRPCCredentials 要求 TLS 支持,否则会报 did not connect: grpc: the credentials require transport level security (use grpc.WithTransportCredentials() to set)

// PerRPCCredentials defines the common interface for the credentials which need to
// attach security information to every RPC (e.g., oauth2).
type PerRPCCredentials interface {
	// GetRequestMetadata gets the current request metadata, refreshing
	// tokens if required. This should be called by the transport layer on
	// each request, and the data should be populated in headers or other
	// context. If a status code is returned, it will be used as the status
	// for the RPC. uri is the URI of the entry point for the request.
	// When supported by the underlying implementation, ctx can be used for
	// timeout and cancellation. Additionally, RequestInfo data will be
	// available via ctx to this call.
	// TODO(zhaoq): Define the set of the qualified keys instead of leaving
	// it as an arbitrary string.
	GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
	// RequireTransportSecurity indicates whether the credentials requires
	// transport security.
	RequireTransportSecurity() bool
}

自定义认证

本节的代码基于 grpc-testing v0.0.1

修改客户端调用


// 定义 PerRPCCredentials
type Token struct {
	APPID  string
	Secret string
	Token  string
}

func (t Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{"app_id": t.APPID, "secret": t.Secret, "token": t.Token}, nil
}

func (t Token) RequireTransportSecurity() bool {
	return true
}


func main() {
	T := Token{
		APPID:  "app-id-testing",
		Secret: "secret-testing",
	}
	// 假设 使用 OAuth2, 此处获取 Token 并启动协程定时刷新 Token
	T.Token = "token-testing"

	creds, err := credentials.NewClientTLSFromFile("./cert/ca.crt", "*.bitlogs.tech")
	if err != nil {
		log.Fatalf("failed to load credentials: %v", err)
	}
	// 添加 WithPerRPCCredentials Option
	conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(creds), grpc.WithBlock(), grpc.WithPerRPCCredentials(T))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := echo.NewEchoClient(conn)
	ctx, cancle := context.WithCancel(context.Background())
	defer cancle()
	r1, err := c.Echo(ctx, &echo.EchoRequest{Msg: "hello"})
	if err != nil {
		log.Fatalf("echo failed :%#v", err)
	}
	log.Println("r1", r1.GetMsg())
	// 调用两次验证是否都传递了 metadata
	r2, err := c.Echo(ctx, &echo.EchoRequest{Msg: "hello"})
	if err != nil {
		log.Fatalf("echo failed :%#v", err)
	}
	log.Println("r2", r2.GetMsg())
}

服务端修改


func (e *EchoServer) Echo(ctx context.Context, par *echo.EchoRequest) (*echo.EchoResponse, error) {
    // 获取 Metadata
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, errors.New("no Token")
	}
	fmt.Println(md)
	// TODO 验证 Token
	fmt.Println(md["token"][0])

	result := &echo.EchoResponse{
		Msg: par.GetMsg(),
	}
	return result, nil
}

func main() {
	log.Println("Go")
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	// 启用 TLS
	creds, err := credentials.NewServerTLSFromFile("./cert/server.crt", "./cert/server.key")
	if err != nil {
		log.Fatal("create server TLS", err)
	}
	s := grpc.NewServer(grpc.Creds(creds))
	echo.RegisterEchoServer(s, &EchoServer{})
	if err := s.Serve(listen); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

以上就是 gRPC 自定义认证的实现过程,还是非常简单的