Bir gRPC sunucunuz olduğunu ve hangi istemcinin onunla iletişim kurmaya çalıştığını belirlemek istediğinizi varsayın. Neyse ki, gRPC sunucusu böyle bir özellik sağlama yeteneğine sahiptir. İstemci, taşıma katmanına bazı bilgiler ekler ve gRPC sunucusu kimlik denetimi yapmak için isteği durdurur. Tüm istemciler kimlik doğrulaması için aynı SSL sertifikalarını kullandığından, yalnızca SSL sorunu çözmez. Bu örnekte, istemcimizi temsil eden statik bir taşıyıcı tokeni kullanacağız ve sunucu, isteği işleme koymadan önce onu kontrol edecektir.


SSL sertifikalar


Her şeyden önce sunucunun SSL sertifikalarını oluşturmanız gerekir.


$ openssl genrsa -out private.key 4096
Generating RSA private key, 4096 bit long modulus
.............++
.............++
(0x10001)

$ openssl req -new -x509 -sha256 -days 1825 -key private.key -out public.crt
Country Name (2 letter code) []:UK
State or Province Name (full name) []:London
Locality Name (eg, city) []:City of London
Organization Name (eg, company) []:You Ltd
Organizational Unit Name (eg, section) []:Engineering
Common Name (eg, fully qualified host name) []:localhost
Email Address []:you@you.com

Yapı


Bir gRPC uygulaması oluşturduğunuzda, önce bir *.proto dosyası oluşturun ve derleyin, ardından uygulamanızı geliştirmeye başlayın.


├── Makefile
├── Readme.md
├── client
│   ├── cert
│   │   └── public.crt
│   └── main.go
├── go.mod
├── go.sum
├── pkg
│   └── proto
│   └── credit
│   ├── credit.pb.go
│   └── credit.proto
└── server
├── cert
│   ├── private.key
│   └── public.crt
└── main.go

Dosyalar


Makefile


.PHONY: compile
compile: ## Compile the proto file.
protoc -I pkg/proto/credit/ pkg/proto/credit/credit.proto --go_out=plugins=grpc:pkg/proto/credit/

.PHONY: server
server: ## Build and run server.
go build -race -ldflags "-s -w" -o bin/server server/main.go
bin/server

.PHONY: client
client: ## Build and run client.
go build -race -ldflags "-s -w" -o bin/client client/main.go
bin/client

credit.proto


syntax = "proto3";

package credit;

message CreditRequest {
float amount = 1;
}

message CreditResponse {
string confirmation = 1;
}

service CreditService {
rpc Credit(CreditRequest) returns (CreditResponse) {}
}

credit.pb.go


İçeriği buraya eklemiyorum çünkü aşağıdaki komutla üretiliyor.


make compile

client/main.go


package main

import (
"context"
"log"
"time"

"github.com/YOU/bank/pkg/proto/credit"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/oauth"
"golang.org/x/oauth2"
)

func main() {
log.Println("Client running ...")

rpcCreds := oauth.NewOauthAccess(&oauth2.Token{AccessToken: "client-x-id"})
trnCreds, err := credentials.NewClientTLSFromFile("./client/cert/public.crt", "localhost")
if err != nil {
log.Fatalln(err)
}

opts := []grpc.DialOption{
grpc.WithTransportCredentials(trnCreds),
grpc.WithPerRPCCredentials(rpcCreds),
}
opts = append(opts, grpc.WithBlock())

conn, err := grpc.Dial(":50051", opts...)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

client := credit.NewCreditServiceClient(conn)

request := &credit.CreditRequest{Amount: 1990.01}

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

response, err := client.Credit(ctx, request)
if err != nil {
log.Fatalln(err)
}

log.Println("Response:", response.GetConfirmation())
}

server/main.go


package main

import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"strings"

"github.com/YOU/bank/pkg/proto/credit"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

type server struct {
credit.UnimplementedCreditServiceServer
}

func main() {
log.Println("Server running ...")

cert, err := tls.LoadX509KeyPair("./server/cert/public.crt", "./server/cert/private.key")
if err != nil {
log.Fatalf("failed to load key pair: %s", err)
}
opts := []grpc.ServerOption{
// Intercept request to check the token.
grpc.UnaryInterceptor(validateToken),
// Enable TLS for all incoming connections.
grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
}

lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}

srv := grpc.NewServer(opts...)
credit.RegisterCreditServiceServer(srv, &server{})

log.Fatalln(srv.Serve(lis))
}

func (s *server) Credit(ctx context.Context, request *credit.CreditRequest) (*credit.CreditResponse, error) {
log.Println(fmt.Sprintf("Request: %g", request.GetAmount()))

return &credit.CreditResponse{Confirmation: fmt.Sprintf("Credited %g", request.GetAmount())}, nil
}

func validateToken(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.InvalidArgument, "missing metadata")
}

if !valid(md["authorization"]) {
return nil, status.Errorf(codes.Unauthenticated, "invalid token")
}

return handler(ctx, req)
}

func valid(authorization []string) bool {
if len(authorization) < 1 {
return false
}

token := strings.TrimPrefix(authorization[0], "Bearer ")

// If you have more than one client then you will have to update this line.
return token == "client-x-id"
}

Test


$ make server
go build -race -ldflags "-s -w" -o bin/server server/main.go
bin/server
2020/04/04 18:07:37 Server running ...

$ make client
go build -race -ldflags "-s -w" -o bin/client client/main.go
bin/client
2020/04/04 18:07:42 Client running ...
2020/04/04 18:07:42 Response: Credited 1990.01

Yukarıdaki istemci kodunu çalıştırdığınızda, sunucu ayrıca aşağıdaki mesajı da verecektir.


2020/04/04 18:07:42 Request: 1990.01

Eğer tokende değişiklik yaparsanız, istemci aşağıdaki mesajı verecektir.


2020/04/04 21:47:21 rpc error: code = Unauthenticated desc = invalid token
make: *** [client] Error 1

Referanslar