Golang笔记 6.3.1 gRPC 使用 metadata 自定义认证

前言

我正在学习酷酷的 Golang,可点此查看帖子Golang学习笔记汇总

1 背景介绍

在 http 请求当中我们可以设置 header 用来传递数据,grpc 底层采用 http2 协议也是支持传递数据的,采用的是 metadata。 Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。

2 用法介绍

2.1 客户端请求中携带 metadata

方法介绍

在 go 语言中,可以用 grpc.WithPerRPCCredentials 方法来实现。

func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption

WithPerRPCCredentials returns a DialOption which sets credentials and places auth state on each outbound RPC.

通常来说,认证信息是需要每次都携带,但如果需要单次携带 metadata,可以使用 metadata.NewOutgoingContext 方法来创建一个携带 metadata 的 context。

Step1. 实例准备

方法的入参是 PerRPCCredentials 接口,因此需要准备一个实例,实现接口的方法。

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

Step2. 通过 DialOption 新建客户端

    opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
    conn, err := grpc.Dial(address, opts...)

2.2 服务端中解析 metadata

从 RPC 消息的上下文中获取 metadata

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
    md, ok := metadata.FromIncomingContext(ctx)
    // do something with metadata
}

3 hello world 示例修改

3.1 客户端

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

func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "appid":  "101010",
        "appkey": "i am key",
    }, nil
}

func (c customCredential) RequireTransportSecurity() bool {
    return false
}
// 上面是第1步,实例准备


func main() {
    var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithBlock())
    // 使用自定义认证
    opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}

3.2 服务端

服务端使用了 metadata 一个的关键函数,它可从 context 中取出 metadata (map 类型):

// FromIncomingContext returns the incoming metadata in ctx if it exists.  The
// returned MD should not be modified. Writing to it may cause races.
// Modification should be made to copies of the returned MD.
func FromIncomingContext(ctx context.Context) (md MD, ok bool) {
	md, ok = ctx.Value(mdIncomingKey{}).(MD)
	return
}

具体处理如下:

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    // 解析metada中的信息并验证
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, 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 nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
    }
	log.Printf("Received: %v.\nToken info: appid=%s,appkey=%s", in.GetName(), appid, appkey)
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

3.3 运行效果

Server 端打印如下信息:

# go run greeter_server/main.go
2019/11/13 12:03:21 Received: world.
Token info: appid=101010,appkey=i am key

4 小结

gRPC 可用 metadata 自定义认证信息。客户端使用 WithPerRPCCredentials 方法,服务端使用 metadata.FromIncomingContext 方法从 RPC 消息的上下文中获取 metadata。

END


发布了237 篇原创文章 · 获赞 226 · 访问量 79万+

猜你喜欢

转载自blog.csdn.net/iotisan/article/details/103056622