Info, handler grpc.UnaryHandler) (resp interface{}, err error)
参数 info 包含了这个 RPC 的所有信息,拦截器可以对其进行操作。 而handler是服务方法实现的包装器。 拦截器负责调用处理程序来完成 RPC。
1、定义一个服务端的鉴权一元拦截器
func ServerUnaryInterceptorCustom() grpc.ServerOption {
serverInterceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
start := time.Now()
// 如果是非登录请求,需要验证token
if info.FullMethod != "/helloservice.HelloService/Login" {
if err := authorize(ctx); err != nil {
return nil, err
}
}
h, err := handler(ctx, req)
log.Printf("Request - Method:%s\tDuration:%s\tError:%v\n",
info.FullMethod,
time.Since(start),
err)
return h, err
}
return grpc.UnaryInterceptor(serverInterceptor)
}
// authorize 从Metadata中获取token并校验是否合法
func authorize(ctx context.Context) error {
// 从context中获取metadata
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
}
func validateToken(token string) error {
// 校验token
return nil
}
我们可以看下我们定义的一元拦截器的执行流程:
- 首先进来之后我们判断如果是登录请求,直接转发请求,并打印日志
- 如果是非登录请求,需要验证token,调用authorize方法
- 在authorize方法中,会从context中获取metadata元数据,然后解析获取token并验证
请注意,前面代码块中的拦截器逻辑使用包 google.golang.org/grpc/codes 和 google.golang.org/grpc/status。
2、grpc客户端传入token
gRPC 支持在客户端和服务器之间使用 Context 值发送元数据。 google.golang.org/grpc/metadata 包提供了元数据的功能。
其中MD类型是一个k-v的map,想下面这样
type MD map[string][]string
下面我们在客户端编写向服务端发送token的代码,我们修改下客户端的unaryRpc,构造包含authorization的metadata:
func unaryRpc(conn *grpc.ClientConn) {
client := helloservice.NewHelloServiceClient(conn)
ctx := context.Background()
// 构造元数据,并返回MD类型的结构
md := metadata.Pairs("authorization", "mytoken")
// 元数据塞入context并返回新的context
ctx = metadata.NewOutgoingContext(ctx, md)
reply, err := client.Hello(ctx, &helloservice.String{Value: "hello"})
if err != nil {
log.Fatal(err)
}
log.Println("unaryRpc recv: ", reply.Value)
}
这样元数据的信息就会跟着context发送到grpc服务端。
接着我们在服务端grpc中修改如下代码,加入一行日志:
func validateToken(token string) error {
log.Printf("get the token: %s \n", token)
// 校验token
return nil
}
3、运行服务
我们重新执行下grpc服务端程序,然后运行下客户端代码,可以看到token传过来了:
go run helloservice/main/main.go
2022/10/15 20:36:04 server started...
2022/10/15 20:36:08 get the token: mytoken
2022/10/15 20:36:09 Request - Method:/helloservice.HelloService/Hello Duration:1.001216763s Error:<nil>
2、StreamServerInterceptor
为了创建 StreamServerInterceptor,通过提供 StreamServerInterceptor func 值作为参数调用 StreamInterceptor 函数,该参数返回为服务器设置 StreamServerInterceptor 的 grpc.ServerOption 值。
func StreamInterceptor(i StreamServerInterceptor) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
if o.streamInt != nil {
panic("The stream server interceptor was already set a