Grpc-interceptor is similar to Gin Middleware. It allows you to perform common operations such as authentication, parameter verification, and traffic limiting before calling RPC services.

A series of

  1. Cloud native API Gateway, GRPC-Gateway V2 Exploration
  2. Go + GRPC-gateway (V2) Construction of micro-service combat series, small program login authentication service: the first chapter
  3. Go + GRPC-Gateway (V2) Construction of micro-service combat series, small program login authentication service: the second chapter
  4. Go + GRPC-gateway (V2) to build micro-service combat series, small program login authentication service (three) : RSA(RS512) signature JWT
  5. Go+ GRPC-gateway (V2) micro-service combat, small program login authentication service (four) : automatic generation of API TS type

grpc.UnaryInterceptor

Starting with VSCode -> Go to Definition, we can see the following source:

// UnaryInterceptor returns a ServerOption that sets the UnaryServerInterceptor for the
// server. Only one unary interceptor can be installed. The construction of multiple
// interceptors (e.g., chaining) can be implemented at the caller.
func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
	return newFuncServerOption(func(o *serverOptions) {
		ifo.unaryInt ! =nil {
			panic("The unary server interceptor was already set and may not be reset.")
		}
		o.unaryInt = i
	})
}
Copy the code

The comment is clear: UnaryInterceptor returns a ServerOption that sets the UnaryServerInterceptor for the gRPC Server. Only one unary interceptor can be installed. Constructs of multiple interceptors (for example, chaining) can be implemented at the caller.

Here we need to implement a method with the following definition:

// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info
// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper
// of the service method implementation. It is the responsibility of the interceptor to invoke handler
// to complete the RPC.
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
Copy the code

The comments are clear: The UnaryServerInterceptor provides a hook to intercept unary RPC execution on the server. Info contains all information about the RPC that the interceptor can manipulate. Handler is a wrapper around the implementation of the Service method. The interceptor’s job is to call the handler to complete the execution of the RPC method. Before calling RPC services, perform common operations (such as authorization) for each micro-service.

Auth Interceptor to write

Describe the business in one sentence:

  • From the request header (header) inauthorizationThe field is passedtokenAnd then throughpubclic.keyVerify that it is valid. Legal putAccountID(claims.subject) attached to the current request context (context).

The core interceptor code is as follows:

type interceptor struct {
	verifier tokenVerifier
}
func (i *interceptor) HandleReq(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    / / get the token
	tkn, err := tokenFromContext(ctx)
	iferr ! =nil {
		return nil, status.Error(codes.Unauthenticated, "")}/ / authentication token
	aid, err := i.verifier.Verify(tkn)
	iferr ! =nil {
		return nil, status.Errorf(codes.Unauthenticated, "token not valid: %v", err)
	}
	// Call the real RPC method
	return handler(ContextWithAccountID(ctx, AccountID(aid)), req)
}
Copy the code

Specific code in the/microsvcs/Shared/auth/auth. Go

TodoMicro service

A todo-list test service.

Here, we add a new microservice called Todo. What we need Todo is: before accessing the Todo RPC Service, we need to check whether it is valid by our authentication Interceptor.

defineproto

todo.proto

syntax = "proto3"; package todo.v1; option go_package="server/todo/api/gen/v1; todopb"; message CreateTodoRequest { string title = 1; } message CreateTodoResponse { } service TodoService { rpc CreateTodo (CreateTodoRequest) returns (CreateTodoResponse); }Copy the code

For simplicity (testing purposes), there is only one field, title.

definegoogle.api.Service

todo.yaml

type: google.api.Service
config_version: 3

http:
  rules:
  - selector: todo.v1.TodoService.CreateTodo
    post: /v1/todo
    body: "*"
Copy the code

Generate relevant code

Microsvcs directory:

sh gen.sh
Copy the code

The following files are generated:

  • microsvcs/todo/api/gen/v1/todo_grpc.pb.go
  • microsvcs/todo/api/gen/v1/todo.pb.go
  • microsvcs/todo/api/gen/v1/todo.pb.gw.go

In the client directory:

sh gen_ts.sh
Copy the code

The following files are generated:

  • client/miniprogram/service/proto_gen/todo/todo_pb.js
  • client/miniprogram/service/proto_gen/todo/todo_pb.d.ts

implementationCreateTodo Service

Specific see: microsvcs/todo/todo/todo. Go

type Service struct {
	Logger *zap.Logger
	todopb.UnimplementedTodoServiceServer
}
func (s *Service) CreateTodo(c context.Context, req *todopb.CreateTodoRequest) (*todopb.CreateTodoResponse, error) {
    // Parse the accountId from the token and perform subsequent operations
	aid, err := auth.AcountIDFromContext(c)
	iferr ! =nil {
		return nil, err
	}
	s.Logger.Info("create trip", zap.String("title", req.Title), zap.String("account_id", aid.String()))
	return nil, status.Error(codes.Unimplemented, "")}Copy the code

Grpc-server startup under reconstruction

We now have multiple services. The Server startup section has a lot of duplication.

Specific code in: microsvcs/Shared/server/GRPC. Go

func RunGRPCServer(c *GRPCConfig) error {
	nameField := zap.String("name", c.Name)
	lis, err := net.Listen("tcp", c.Addr)
	iferr ! =nil {
		c.Logger.Fatal("cannot listen", nameField, zap.Error(err))
	}
	var opts []grpc.ServerOption
	// Auth interceptor is not required to authenticate microservices
	ifc.AuthPublicKeyFile ! ="" {
		in, err := auth.Interceptor(c.AuthPublicKeyFile)
		iferr ! =nil {
			c.Logger.Fatal("cannot create auth interceptor", nameField, zap.Error(err))
		}
		opts = append(opts, grpc.UnaryInterceptor(in))
	}
	s := grpc.NewServer(opts...)
	c.RegisterFunc(s)
	c.Logger.Info("server started", nameField, zap.String("addr", c.Addr))
	return s.Serve(lis)
}
Copy the code

Next, the grPC-server startup code for other microservices looks much better:

The code is at: todo/main.go

logger.Sugar().Fatal(
    server.RunGRPCServer(&server.GRPCConfig{
    	Name:              "todo",
    	Addr:              ": 8082",
    	AuthPublicKeyFile: "shared/auth/public.key",
    	Logger:            logger,
    	RegisterFunc: func(s *grpc.Server) {
    		todopb.RegisterTodoServiceServer(s, &todo.Service{
    			Logger: logger,
    		})
    	},
    }),
)
Copy the code

The code is at: auth/main.go

logger.Sugar().Fatal(
	server.RunGRPCServer(&server.GRPCConfig{
		Name:   "auth",
		Addr:   ": 8081",
		Logger: logger,
		RegisterFunc: func(s *grpc.Server) {
			authpb.RegisterAuthServiceServer(s, &auth.Service{
				OpenIDResolver: &wechat.Service{
					AppID:     "your-appid",
					AppSecret: "your-appsecret",
				},
				Mongo:          dao.NewMongo(mongoClient.Database("grpc-gateway-auth")),
				Logger:         logger,
				TokenExpire:    2 * time.Hour,
				TokenGenerator: token.NewJWTTokenGen("server/auth", privKey),
			})
		},
	}),
)
Copy the code

alignment

Reconstruct the Gateway Server

We are going to reverse proxy multiple gRPC server endpoints.

Specific code in: microsvcs/gateway/main. Go

serverConfig := []struct {
	name         string
	addr         string
	registerFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error)
}{
	{
		name:         "auth",
		addr:         "localhost:8081",
		registerFunc: authpb.RegisterAuthServiceHandlerFromEndpoint,
	},
	{
		name:         "todo",
		addr:         "localhost:8082",
		registerFunc: todopb.RegisterTodoServiceHandlerFromEndpoint,
	},
}

for _, s := range serverConfig {
	err := s.registerFunc(
		c, mux, s.addr,
		[]grpc.DialOption{grpc.WithInsecure()},
	)
	iferr ! =nil {
		logger.Sugar().Fatalf("cannot register service %s : %v", s.name, err)
	}
}
addr := ": 8080"
logger.Sugar().Infof("grpc gateway started at %s", addr)
logger.Sugar().Fatal(http.ListenAndServe(addr, mux))
Copy the code

test

Refs

  • grpc-ecosystem/go-grpc-middleware
  • API Security : API key is dead.. Long live Distributed Token by value
  • Demo: go-grpc-gateway-v2-microservice
  • gRPC-Gateway
  • gRPC-Gateway Docs
I am weishao wechat: uuhells123 public number: hackers afternoon tea add my wechat (mutual learning exchange), pay attention to the public number (for more learning materials ~)Copy the code