如何在启动gRPC服务器前获取自定义方法Scope选项?
gRPC自定义Scope初始化死循环问题的解决方法
问题场景
我正尝试为gRPC方法添加自定义Scope以保护服务暴露,实现如下:
Scope定义的Protobuf代码
syntax = "proto3"; package grpc.proto.scopes; import "google/protobuf/descriptor.proto"; extend google.protobuf.MethodOptions { string scopes = 50000; }
服务中使用Scope的Protobuf代码
syntax = "proto3"; import "grpc/proto/scopes.proto"; package service; service Service1 { rpc Get(Request) returns (Response) { option(grpc.proto.scopes.scopes) = "service1.feature.read"; } rpc Post(Request) returns (Response) { option(grpc.proto.scopes.scopes) = "service1.feature.write"; } }
遇到的初始化死循环
初始化gRPC服务器时必须传入依赖Scope的拦截器,但只有调用proto.RegisterService1Server注册服务后,Scope配置才会进入注册表,导致无法提前初始化拦截器,陷入死循环。不想用protoregistry.GlobalFiles.FindDescriptorByName实时查找的方式,因为会降低性能。
可行解决方案
1. 提前从编译的FileDescriptor提取Scope(推荐)
gRPC编译生成的.pb.go文件中包含了服务的FileDescriptor,可以直接在启动阶段加载这个描述符,遍历提取所有方法的Scope配置,无需依赖服务注册或全局注册表。
示例代码:
import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" pb "your/service/proto/path" // 替换为你的服务proto包路径 scopespb "your/grpc/proto/scopes/path" // 替换为Scope定义的proto包路径 ) // 预加载所有方法的Scope配置 func loadScopeConfig() map[string]string { scopeMap := make(map[string]string) fileDesc := pb.File_service_proto // 编译生成的FileDescriptor变量 // 遍历文件中的所有服务 fileDesc.Services().Range(func(svc protoreflect.ServiceDescriptor) bool { // 遍历服务的所有方法 svc.Methods().Range(func(method protoreflect.MethodDescriptor) bool { // 提取自定义Scope选项 opts := method.Options().(*proto.MethodOptions) if scope := scopespb.E_Scopes.Get(opts); scope != "" { // 用方法的全限定名作为key(格式:"服务全名/方法名") fullMethodName := string(svc.FullName()) + "/" + string(method.Name()) scopeMap[fullMethodName] = scope } return true }) return true }) return scopeMap } // 初始化拦截器 func newScopeInterceptor(scopeMap map[string]string) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { // 通过方法全限定名获取对应的Scope if requiredScope, ok := scopeMap[info.FullMethod]; ok { // 执行Scope校验逻辑(比如验证请求中的权限是否包含该Scope) // if !hasPermission(ctx, requiredScope) { // return nil, status.Error(codes.PermissionDenied, "insufficient scope") // } } return handler(ctx, req) } } // 启动服务器 func main() { // 提前加载Scope配置 scopeMap := loadScopeConfig() // 初始化拦截器 interceptor := newScopeInterceptor(scopeMap) // 创建正式服务器并传入拦截器 server := grpc.NewServer(grpc.UnaryInterceptor(interceptor)) // 注册服务 pb.RegisterService1Server(server, &yourServiceImplementation{}) // 启动服务器监听... }
2. 通过临时服务器完成注册提取Scope
先创建一个临时gRPC服务器,注册服务后从全局注册表提取Scope配置,再用这些配置初始化拦截器并创建正式服务器。
示例代码:
import ( "google.golang.org/grpc" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/reflect/protoreflect" pb "your/service/proto/path" scopespb "your/grpc/proto/scopes/path" ) // 初始化拦截器 func initScopeInterceptor() grpc.UnaryServerInterceptor { // 创建临时服务器(仅用于注册服务,不会实际处理请求) tempServer := grpc.NewServer() // 注册服务(用空实现即可,只需满足接口) pb.RegisterService1Server(tempServer, &mockService{}) scopeMap := make(map[string]string) // 遍历服务的所有方法描述符 for _, methodDesc := range pb.Service_Service1ServiceDesc.Methods { // 通过方法全限定名查找Descriptor methodRefl, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodDesc.FullMethodName)) if err != nil { panic(err) // 根据实际情况处理错误 } method := methodRefl.(protoreflect.MethodDescriptor) // 提取Scope配置 opts := method.Options().(*proto.MethodOptions) if scope := scopespb.E_Scopes.Get(opts); scope != "" { scopeMap[methodDesc.FullMethodName] = scope } } // 返回配置好的拦截器 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if requiredScope, ok := scopeMap[info.FullMethod]; ok { // 执行Scope校验逻辑 // ... } return handler(ctx, req) } } // 空实现的mock服务,仅用于满足注册接口 type mockService struct{} func (m *mockService) Get(ctx context.Context, req *pb.Request) (*pb.Response, error) { return nil, nil } func (m *mockService) Post(ctx context.Context, req *pb.Request) (*pb.Response, error) { return nil, nil } // 启动服务器 func main() { interceptor := initScopeInterceptor() server := grpc.NewServer(grpc.UnaryInterceptor(interceptor)) pb.RegisterService1Server(server, &realServiceImplementation{}) // 启动服务器监听... }
内容的提问来源于stack exchange,提问作者Loïc Madiès
相关产品推荐
相关产品推荐

