62

golang中使用gRPC的拦截器 · 远山淡影

 4 years ago
source link: https://blog.ache.fun/2018/11/04/golang%E4%B8%AD%E4%BD%BF%E7%94%A8gRPC%E7%9A%84%E6%8B%A6%E6%88%AA%E5%99%A8/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Nov 4, 2018

最近工作使用gRPC来做通信,一开始在研究认证时,发现了gRPC的拦截器,在这里记录下其用法,后续备忘

拦截器的分类

在gRPC中有两种拦截器UnaryInterceptorStreamInterceptor,其中UnaryInterceptor拦截普通的一次请求一次响应的rpc服务,StreamInterceptor拦截流式的rpc服务。

Server

在包google.golang.org/grpc中,给Server提供了两个适用于Unary和Stream的拦截器函数分别是UnaryInterceptorStreamInterceptor

UnaryInterceptor

UnaryInterceptor函数的唯一参数是一个UnaryServerInterceptor

func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
......
}

UnaryInterceptor返回的ServerOption作为grpc.NewServer的参数。

而UnaryServerInterceptor是一个函数类型:

type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

其中四个参数分别是

  • ctx: 当前请求的Context
  • req:当前请求的参数
  • info:当前请求的服务端信息
  • handler:服务端处理这次请求的handler

来自shijuvar@medium的一个简单的例子:

// Authorization unary interceptor function to handle authorize per RPC call
func serverInterceptor(ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
// Skip authorize when GetJWT is requested
if info.FullMethod != "/proto.EventStoreService/GetJWT" {
if err := authorize(ctx); err != nil {
return nil, err
}
}

// Calls the handler
h, err := handler(ctx, req)

// Logging with grpclog (grpclog.LoggerV2)
grpcLog.Infof("Request - Method:%s\tDuration:%s\tError:%v\n",
info.FullMethod,
time.Since(start),
err)

return h, err
}

// authorize function authorizes the token received from Metadata
func authorize(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Errorf(codes.InvalidArgument, "Retrieving metadata is failed")
}

authHeader, ok := md["authorization"]
if !ok {
return status.Errorf(codes.Unauthenticated, "Authorization token is not supplied")
}

token := authHeader[0]
// validateToken function validates the token
err := validateToken(token)

if err != nil {
return status.Errorf(codes.Unauthenticated, err.Error())
}
return nil
}

gRPC支持在Context中发送metadata, google.golang.org/grpc/metadata包提供了操作metadata的功能。

Client

在Client中全局传递metadata

func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption {
......
}

其中参数是个interface:

type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}

最后示例代码:

// customCredential 自定义认证
type customCredential struct{}
// GetRequestMetadata 实现自定义认证接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"authorization": "xxxxx",
}, nil
}
// RequireTransportSecurity 自定义认证是否开启TLS
func (c customCredential) RequireTransportSecurity() bool {
return false
}

func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithPerRPCCredentials(new(customCredential)))
if err != nil {
log.Fatalf("Dial error: %+v\n", err)
}
defer conn.Close()
c := pb.NewExpStreamClient(conn)
r, err := c.RpcMethod(context.Background()....)
if err != nil {
log.Fatalf("say unary error %v\n", err)
}
}

除了通过grpc.WithPerRPCCredentials全局传递metadata外,还可以通过每次context传递metadata

ctx := context.Background()
md := metadata.Pairs("authorization", jwtToken)
ctx = metadata.NewOutgoingContext(ctx, md)
// Calls RPC method CreateEvent using the stub client
resp, err := client.CreateEvent(context.Background(), event)

go-grpc-middleware

go-grpc-middleware包装了一些列拦截器的链式操作,示例代码如下:

grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(loggingUnary, monitoringUnary, authUnary),
)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK