我们有一个简单的 gRPC 服务:
message GetUserRequest { string id = 1; } message GetUserResponse { string id = 1; string name = 2; string email = 3; } service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse); }
gRPC 在很多方面都很棒,它的性能很棒,它的生态系统是无法比较的。
但恕我直言,最重要的是它提供的打字合同。
我是一名后端工程师,我和我的移动和网络朋友可以坐下来讨论,达成协议,然后我们在 flutter 和 es 中生成存根客户端代码进行模拟实现,3天后重新组合。
美好而有表现的一天!
但是等等,我们漏掉了一些东西!
service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse); }
GetUser方法几乎具备了一切,但还不足以描述授权需求。
我们只缺少角色部分来描述 RBAC 规则。
太糟糕了,我们已经很接近了,只要我们能做点什么,类型安全的东西,通用的东西......?
在原型描述符的帮助下,你的愿望将会实现
enum Role { ROLE_UNSPECIFIED = 0; ROLE_CUSTOMER = 1; ROLE_ADMIN = 2; } message RoleBasedAccessControl { repeated Role allowed_roles = 1; bool allow_unauthenticated = 2; } extend google.protobuf.MethodOptions { optional RoleBasedAccessControl access_control = 90000; // I don't know about this 90000, seem as long as it's unique }
service UsersService { rpc GetUser(GetUserRequest) returns (GetUserResponse) { option (components.rbac.v1.access_control) = { allowed_roles: [ ROLE_CUSTOMER, ROLE_ADMIN ] allow_unauthenticated: false }; } rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse) { option (components.rbac.v1.access_control) = { allowed_roles: [ROLE_ADMIN] allow_unauthenticated: false }; } }
开个玩笑,夜深了,我需要在 08:00 AM 之前到达办公室?
加载方法描述符:
func RBACUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { // the info give us: /components.users.v1.UsersService/GetUser // we need to convert it to components.users.v1.UsersService.GetUser methodName := strings.Replace(strings.TrimPrefix(info.FullMethod, "/"), "/", ".", -1) desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(methodName)) if err != nil { return nil, status.Errorf(codes.Internal, "method not found descriptor") } method, ok := desc.(protoreflect.MethodDescriptor) if !ok { return nil, status.Errorf(codes.Internal, "some hoe this is not a method") }
找到我们的 access_control 选项:
var policy *rbacv1.RoleBasedAccessControl method.Options().ProtoReflect().Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { if fd.FullName() != rbacv1.E_AccessControl.TypeDescriptor().FullName() { // continue finding the AccessControl field return true } b, err := proto.Marshal(v.Message().Interface()) if err != nil { // TODO: better handle this as an Internal error // but for now, just return PermissionDenied return false } policy = &rbacv1.RoleBasedAccessControl{} if err := proto.Unmarshal(b, policy); err != nil { // same as above, better handle this as an Internal error return false } // btw I think this policy can be cached return false }) if policy == nil { // secure by default, DENY_ALL if no control policy is found return nil, status.Errorf(codes.PermissionDenied, "permission denied") }
将新创建的拦截器添加或附加到您的后端
func newUsersServer() *grpc.Server { svc := grpc.NewServer(grpc.UnaryInterceptor(interceptors.RBACUnaryInterceptor)) usersv1.RegisterUsersServiceServer(svc, &usersServer{}) return svc }
然后测试一下:
peasantCtx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("role", "ROLE_CUSTOMER")) _, err = client.GetUser(peasantCtx, &usersv1.GetUserRequest{}) fmt.Println(status.Code(err)) _, err = client.DeleteUser(peasantCtx, &usersv1.DeleteUserRequest{}) fmt.Println(status.Code(err)) knightlyAdminCtx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("role", "ROLE_ADMIN")) _, err = client.GetUser(knightlyAdminCtx, &usersv1.GetUserRequest{}) fmt.Println(status.Code(err)) _, err = client.DeleteUser(knightlyAdminCtx, &usersv1.DeleteUserRequest{}) fmt.Println(status.Code(err)) // Output: // OK // PermissionDenied // OK // OK
最后,这里发布了带有可测试示例的示例代码 https://github.com/nvcnvn/grpc-methods-descriptor-example
以上是使用 RBAC 保护您的 gRPC 服务的原始定义的详细内容。更多信息请关注PHP中文网其他相关文章!