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