gRPC 进阶——利用 Metadata 和 Interceptor 实现 auth 认证

gRPC 是一个高性能的开源 RPC 框架,支持多种编程语言,并且基于 HTTP/2 协议提供了双向流和头部压缩等特性。在构建分布式系统和微服务架构时,安全认证是至关重要的一环。通过利用 gRPC 的 Metadata 和拦截器机制,可以实现灵活且强大的认证解决方案。

利用Metadata和Interceptor可以实现gRPC的auth认证,下面将教你用两种方法实现:

1.利用Metadata和Interceptor

0.目录树如下

以grpc入门的代码作为示例:

.
├── client
│   └── client.go
├── proto
│   ├── helloworld_grpc.pb.go
│   ├── helloworld.pb.go
│   └── helloworld.proto
└── server
    └── server.go
1. proto文件
syntax = "proto3";

option go_package = "grpc/proto";

service Hello {
  rpc Hello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string response = 1;
}

利用命令生成对应的go文件

cd proto/
protoc meta.proto --go_out=. --go-grpc_out=.
2.server/server.go
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"

	"go-try/grpc_auth/proto"
)

type HelloServer struct {
	proto.UnimplementedHelloServer
}

func (s *HelloServer) Hello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	return &proto.HelloResponse{
		Response: "Hello " + request.Name,
	}, nil
}

var interceptor = func(ctx context.Context, req any,
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
	fmt.Println("接收到了一个新的请求")
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return resp, status.Error(codes.Unauthenticated, "无token认证信息")
	}

	appid, ok1 := md["appid"]
	appkey, ok2 := md["appkey"]
	if !ok1 || !ok2 || appid[0] != "10010" || appkey[0] != "auth token" {
		return resp, status.Error(codes.Unauthenticated, "token认证失败")
	}

	fmt.Println("appid: ", appid, " appkey: ", appkey)
	response, _ := handler(ctx, req)
	fmt.Println("请求已经完成")
	return response, nil
}

func main() {
	opts := grpc.UnaryInterceptor(interceptor)

	server := grpc.NewServer(opts)
	proto.RegisterHelloServer(server, &HelloServer{})
	listen, err := net.Listen("tcp", "0.0.0.0:8080")
	if err != nil {
		panic("faild to listen : " + err.Error())
	}
	err = server.Serve(listen)
	if err != nil {
		panic("faild to start grpc : " + err.Error())
	}
}

3.client/client.go
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc/metadata"
	"time"

	"google.golang.org/grpc"

	"go-try/grpc_auth/proto"
)

var interceptor = func(ctx context.Context, method string,
	req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	// 记录请求开始的时间
	start := time.Now()
	// 创建元数据
	md := metadata.New(map[string]string{
		"appid":  "10010",
		"appkey": "auth token",
	})
	// 将元数据添加到上下文中
	ctx = metadata.NewOutgoingContext(context.Background(), md)
	// 调用方法
	err := invoker(ctx, method, req, reply, cc, opts...)
	// 打印请求耗时
	fmt.Printf("请求耗时: %s\n", time.Since(start))
	// 返回调用方法的错误
	return err
}

func main() {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	opts = append(opts, grpc.WithUnaryInterceptor(interceptor))

	conn, err := grpc.NewClient("0.0.0.0:8080", opts...)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	cli := proto.NewHelloClient(conn)
	msg, err := cli.Hello(context.Background(), &proto.HelloRequest{Name: "zs"})
	if err != nil {
		panic(err)
	}
	fmt.Println(msg)
}

4.分别运行server.go和client.go

server端运行结果如下:

接收到了一个新的请求
appid:  [10010]  appkey:  [auth token]
请求已经完成

如果我们将client.go文件中的appid修改:

md := metadata.New(map[string]string{
		"appid":  "10010",
		"appkey": "auth token",
	})

修改为

md := metadata.New(map[string]string{
		"appid":  "100101",
		"appkey": "auth token",
	})

再次分别运行server.go和client.go

server端运行结果会显示:

接收到了一个新的请求

client端运行结果会显示报错:

请求耗时: 3.539849ms
panic: rpc error: code = Unauthenticated desc = token认证失败

由此说明,利用Metadata和Interceptor已经实现了较为简单的auth认证。

2.利用go中grpc自带的函数

1.修改client/client.go代码
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"

	"go-try/grpc_auth/proto"
)

type customCredential struct{}

func (cc *customCredential) GetRequestMetadata(ctx context.Context,
	uri ...string) (map[string]string, error) {
	return map[string]string{
		"appid":  "10010",
		"appkey": "auth token",
	}, nil
}

// RequireTransportSecurity indicates whether the credentials requires
// transport security.
func (cc *customCredential) RequireTransportSecurity() bool {
	return false
}

func main() {
	var opts []grpc.DialOption
	opts = append(opts, grpc.WithInsecure())
	//opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
	opts = append(opts, grpc.WithPerRPCCredentials(&customCredential{}))

	conn, err := grpc.NewClient("0.0.0.0:8080", opts...)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	cli := proto.NewHelloClient(conn)
	msg, err := cli.Hello(context.Background(), &proto.HelloRequest{Name: "zs"})
	if err != nil {
		panic(err)
	}
	fmt.Println(msg)
}

2.分别运行server.go和client.go

server端运行结果如下:

接收到了一个新的请求
appid:  [10010]  appkey:  [auth token]
请求已经完成

如果我们将client.go文件中的appid修改:

md := metadata.New(map[string]string{
		"appid":  "10010",
		"appkey": "auth token",
	})

修改为

md := metadata.New(map[string]string{
		"appid":  "100101",
		"appkey": "auth token",
	})

再次分别运行server.go和client.go

server端运行结果会显示:

接收到了一个新的请求

client端运行结果会显示报错:

请求耗时: 3.539849ms
panic: rpc error: code = Unauthenticated desc = token认证失败

由此说明,利用go中grpc自带的WithPerRPCCredentials()也可以实现较为简单的auth认证。

总结

客户端实现流程

客户端通过创建一个自定义的拦截器,在每个 RPC 调用之前将认证令牌(例如 OAuth 令牌)添加到请求的 Metadata 中。这样可以确保每个请求都包含了有效的认证信息,并且可以在需要时自动处理认证逻辑,提高了安全性和代码的复用性。

服务器实现流程

服务器则通过设置一个拦截器来截取每个传入的 RPC 请求,并验证其中的认证信息。通过解析传入请求的 Metadata,服务器可以检查认证令牌的有效性或者其他自定义的认证机制。如果认证失败,服务器可以返回相应的错误代码,如 codes.Unauthenticated,以保护服务不受未经授权的访问。

通过这种方式,gRPC 提供了一种强大而灵活的认证机制,可以根据具体需求定制和扩展,确保在分布式系统中的通信安全和可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值