通过拦截器和 Metadata 实现 gRPC 的 Auth 认证


文章目录

  • 通过拦截器和 Metadata 实现 gRPC 的 Auth 认证
  • 1. 创建 Protocol Buffers 文件
  • 2. 实现 gRPC 服务端
  • 3. 实现自定义认证拦截器
  • 4. 实现 gRPC 客户端


在 gRPC 服务中实现基于拦截器和 Metadata 的认证。我们将使用一个简单的 Greeter 服务来演示该过程。

  1. 创建 Protocol Buffers 文件
  2. 实现 gRPC 服务端
  3. 实现自定义认证拦截器
  4. 实现 gRPC 客户端

1. 创建 Protocol Buffers 文件

首先,我们定义了一个简单的 Greeter 服务,它包含一个 SayHello 方法。该方法接受 HelloRequest 请求并返回 HelloReply 响应。以下是 greeter.proto 文件的内容:

syntax = "proto3";
option go_package = ".;proto";

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

2. 实现 gRPC 服务端

接下来,我们实现 gRPC 服务端。在服务端中,我们将使用自定义的认证拦截器来验证请求中的 Metadata。以下是服务端的实现代码:

package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"net"

	"OldPackageTest/grpc_token_auth_test/proto"
)

// Server 结构体实现了 proto.UnimplementedGreeterServer 接口
type Server struct {
	proto.UnimplementedGreeterServer
}

// SayHello 方法实现了 Greeter 服务的 SayHello RPC 方法
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	// 定义拦截器,用于在处理每个请求之前和之后执行自定义逻辑
	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		fmt.Println("接收到了一个新的请求")
		
		// 从上下文中提取元数据
		md, ok := metadata.FromIncomingContext(ctx)
		fmt.Println(md)
		if !ok {
			// 如果没有元数据,则返回未认证错误
			return resp, status.Error(codes.Unauthenticated, "无token认证信息")
		}
		
		// 从元数据中提取 appid 和 appkey
		var (
			appid  string
			appkey string
		)
		if va1, ok := md["appid"]; ok {
			appid = va1[0]
		}
		if va1, ok := md["appkey"]; ok {
			appkey = va1[0]
		}
		
		// 检查 appid 和 appkey 是否匹配预期值
		if appid != "101010" || appkey != "i am a key" {
			return resp, status.Error(codes.Unauthenticated, "无token认证信息")
		}

		// 如果认证通过,调用实际的 RPC 方法处理请求
		res, err := handler(ctx, req)
		fmt.Println("请求已经完成")
		return res, err
	}

	// 创建一个新的 gRPC 服务器,并应用拦截器
	opt := grpc.UnaryInterceptor(interceptor)
	g := grpc.NewServer(opt)
	
	// 注册 Greeter 服务
	proto.RegisterGreeterServer(g, &Server{})
	
	// 监听 TCP 端口
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	
	// 启动 gRPC 服务器
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}

3. 实现自定义认证拦截器

在上面的代码中,我们实现了一个自定义的认证拦截器 interceptor。该拦截器从请求的 Metadata 中提取 appidappkey,并验证它们是否匹配预期的值。如果认证失败,拦截器将返回一个未认证错误;如果认证成功,拦截器将继续处理请求。

4. 实现 gRPC 客户端

接下来,我们实现 gRPC 客户端。客户端使用自定义的凭据类 customCredential 在每个请求中添加认证信息。以下是客户端的实现代码:

package main

import (
	"OldPackageTest/grpc_token_auth_test/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
)

// customCredential 结构体用于实现 gRPC 的自定义认证凭据
type customCredential struct{}

// GetRequestMetadata 方法返回请求的元数据(包括认证信息)
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid":  "101010",
		"appkey": "i am a key",
	}, nil
}

// RequireTransportSecurity 方法指示是否需要传输层安全性,这里返回 false 表示不需要
func (c customCredential) RequireTransportSecurity() bool {
	return false
}

func main() {
	// 创建一个包含自定义凭据的 gRPC 选项
	grpc.WithPerRPCCredentials(customCredential{})
	
	// 定义 gRPC 连接选项
	var opts []grpc.DialOption
	// 使用不安全连接(不加密)
	opts = append(opts, grpc.WithInsecure())
	// 添加自定义凭据选项
	opts = append(opts, grpc.WithPerRPCCredentials(customCredential{}))
	
	// 连接到 gRPC 服务器
	conn, err := grpc.Dial("127.0.0.1:50051", opts...)
	if err != nil {
		panic(err) // 连接失败时,终止程序并输出错误信息
	}
	defer conn.Close() // 在函数返回前关闭连接

	// 创建 Greeter 客户端
	c := proto.NewGreeterClient(conn)
	
	// 向服务器发送 SayHello 请求,并等待响应
	r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err) // 请求失败时,终止程序并输出错误信息
	}
	
	// 输出服务器返回的消息
	fmt.Println(r.Message)
}

在上面的代码中,我们实现了 customCredential 结构体,它实现了 grpc.PerRPCCredentials 接口。该接口包含两个方法:

  • GetRequestMetadata:返回请求的元数据(包括 appidappkey)。
  • RequireTransportSecurity:指示是否需要传输层安全性。

我们使用 grpc.WithPerRPCCredentials(customCredential{}) 创建 gRPC 选项,并在连接服务器时应用这些选项。

注:grpc.WithInsecure()已经不再使用该使用为 grpc.WithTransportCredentials(insecure.NewCredentials() 博客地址