go-grpc实践指南-04拦截器

interceptor拦截器

grpc服务端和客户端都提供了interceptor功能,功能类似middleware,很适合在这里处理验证、日志等流程。

在自定义Token认证的示例中,认证信息是由每个服务中的方法处理并认证的,如果有大量的接口方法,这种姿势就太不优雅了,每个接口实现都要先处理认证信息。这个时候interceptor就可以用来解决了这个问题,在请求被转到具体接口之前处理认证信息,一处认证,到处无忧。在客户端,我们增加一个请求日志,记录请求相关的参数和耗时等等。修改hello_token项目实现:

目录结构

示例代码:

Step 1. 服务端interceptor:

hello_interceptor/server/main.go

点击查看代码
package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"io/ioutil"
	"log"
	"my_grpc/proto/hello"
	"net"
)

const (
	// Address grpc服务地址
	Address = ":9000"
)

// 定义helloService并实现约定的接口
type helloService struct {}

// HelloService Hello服务
var HelloService = new(helloService)

// SayHello 实现Hello服务接口
func (*helloService) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
	rsp := new(hello.HelloResponse)
	rsp.Message = "hello: " + in.Name
	return rsp, nil
}

func main() {
	var opts []grpc.ServerOption
	// 使用tls进行加载key pair对
	certifacate, err := tls.LoadX509KeyPair("keys/server/server.pem", "keys/server/server.key")
	if err != nil {
		log.Fatalln("tls加载X509失败,err:",err)
	}
	// 创建证书池
	certPool := x509.NewCertPool()
	// 向证书池中加入证书
	certBytes, err := ioutil.ReadFile("keys/ca/ca.crt")
	if err != nil {
		log.Fatalln("读取ca.crt证书失败,err:", err)
	}
	// 加载证书从pem文件里面
	certPool.AppendCertsFromPEM(certBytes)
	// 创建credentials对象
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{certifacate},  // 服务端证书
		ClientAuth: tls.RequireAndVerifyClientCert,  // 需要并且验证客户端证书
		ClientCAs: certPool,  // 客户端证书池
	})
	opts = append(opts, grpc.Creds(creds))

	// 注册interceptor
	opts = append(opts, grpc.UnaryInterceptor(interceptor))

	listen, _ := net.Listen("tcp", Address)
	gServer := grpc.NewServer(opts...)
	hello.RegisterHelloServer(gServer, HelloService)
	fmt.Println("Listen ont 9000 with TLS + Token")
	_ = gServer.Serve(listen)
}

// interceptor拦截器
func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	err := auth(ctx)
	if err != nil {
		return nil, err
	}
	// 继续处理请求
	return handler(ctx, req)
}

func auth(ctx context.Context) error {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return grpc.Errorf(codes.Unauthenticated, "无token认证信息")
	}
	var (
		appid string
		appkey string
	)
	if val, ok := md["appid"]; ok {
		appid = val[0]
	}
	if val, ok := md["appkey"]; ok {
		appkey = val[0]
	}
	if appid != "101010" || appkey != "i am key" {
		return grpc.Errorf(codes.Unauthenticated, "Token认识信息无效:appid=%s, appkey=%s", appid, appkey)
	}
	return nil
}

Step 2. 实现客户端interceptor:

hello_intercepror/client/main.go

点击查看代码
package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/insecure"
	"io/ioutil"
	"log"
	"my_grpc/proto/hello"
	"time"
)

const (
	// Address grpc服务地址
	Address = ":9000"

	// OpenTLS 是否开启TLS认证
	OpenTLS = true
)

// 自定义认证
type customCredential struct {}

// GetRequestMetadata 实现自定义认证接口
func (*customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid": "101010",
		"appkey": "i am key",
	}, nil
}

// RequireTransportSecurity 自定义认证是否开启TLS
func (*customCredential) RequireTransportSecurity() bool {
	return OpenTLS
}

func main() {
	var opts []grpc.DialOption

	if OpenTLS {
		// TLS链接
		cert, err := tls.LoadX509KeyPair("keys/client/client.pem", "keys/client/client.key")
		if err != nil {
			log.Fatalln("tls.LoadX509 err:", err)
		}
		certPool := x509.NewCertPool()
		certBytes, err := ioutil.ReadFile("keys/ca/ca.crt")
		if err != nil {
			log.Fatalln("读取ca证书失败:", err)
		}
		certPool.AppendCertsFromPEM(certBytes)
		tcreds := credentials.NewTLS(&tls.Config{
			Certificates: []tls.Certificate{cert},// 放入客户端证书
			ServerName: "localhost", //证书里面的 commonName
			RootCAs: certPool, // 证书池
		})
		creds := grpc.WithTransportCredentials(tcreds)
		opts = append(opts, creds)
	}else {
		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	}

	// 指定自定义认证
	opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))

	// 指定客户端interceptor拦截器
	opts = append(opts, grpc.WithUnaryInterceptor(interceptor))

	clientConn, err := grpc.Dial(":9000", opts...)
	if err != nil {
		log.Fatalln("grpc.Dial err:", err)
	}
	helloClient := hello.NewHelloClient(clientConn)
	reply, err := helloClient.SayHello(context.Background(), &hello.HelloRequest{Name: "gRPC"})
	if err != nil {
		log.Fatalln("helloClient.SayHello err:", err)
	}
	fmt.Println(reply)
}

// interceptor 客户端拦截器
func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	start := time.Now()
	err := invoker(ctx, method, req, reply, cc, opts...)
	log.Printf("method=%s req=%v reply=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err)
	return err
}
### 运行结果 ``` $ cd hello_inteceptor/server && go run main.go Listen on 127.0.0.1:50052 with TLS + Token + Interceptor ``` ``` $ cd hello_inteceptor/client && go run main.go method=/hello.Hello/SayHello req=name:"gRPC" rep=message:"Hello gRPC." duration=33.879699ms error= Hello gRPC. ```

这个项目对interceptor进行了封装,支持多个拦截器的链式组装,对于需要多种处理的地方使用起来会更方便些。

posted @ 2022-09-26 11:31  专职  阅读(131)  评论(0编辑  收藏  举报