You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go gRPC服务授权优化:如何避免各方法的重复代码?

Great question—this is a super common pain point when building gRPC services with auth, and there are a few solid patterns to eliminate that repetitive permission-check code. Let’s break down the most practical approaches:

1. Centralize Authorization in a Dedicated gRPC Interceptor (For Generic Permissions)

Since you’re already using a grpc.UnaryServerInterceptor to parse JWTs, you can extend this (or add a separate dedicated interceptor) to handle permission checks globally. The key here is attaching metadata to your gRPC methods (via proto method options) to define what permissions are required, then letting the interceptor validate those against the JWT claims in the context.

Step 1: Define a Custom Proto Method Option

First, create a proto extension to mark which permissions a method needs:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MethodOptions {
  string required_permission = 50001; // Use a unique field number
}

service MyService {
  rpc GetSomething(GetSomethingRequest) returns (GetSomethingResponse) {
    option (required_permission) = "resource:read";
  }
  rpc UpdateSomething(UpdateSomethingRequest) returns (UpdateSomethingResponse) {
    option (required_permission) = "resource:write";
  }
}

Step 2: Update Your Interceptor to Validate Permissions

In your Go interceptor, extract the method’s required permission from the proto metadata and check it against the JWT claims in the context:

import (
  "google.golang.org/grpc"
  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"
  "google.golang.org/protobuf/reflect/protoreflect"
)

// Map your proto extension to a method option key
var requiredPermissionKey = protoreflect.MethodOptionKey("required_permission")

func AuthorizationInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
  // Extract JWT claims from context (your existing logic)
  claims, ok := ctx.Value(ctxKey).(MyStruct)
  if !ok {
    return nil, status.Errorf(codes.Unauthenticated, "invalid authentication")
  }

  // Get the method's custom permission option
  methodDesc := info.Method
  methodOpts := methodDesc.Options.(protoreflect.MethodOptions)
  permValue := methodOpts.Get(requiredPermissionKey)

  if permValue.IsValid() {
    requiredPerm := permValue.String()
    // Check if the user has the required permission (adapt your existing logic here)
    if !claims.HasPermission(requiredPerm) {
      return nil, status.Errorf(codes.PermissionDenied, "insufficient permissions")
    }
  }

  // Pass through to the handler if authorized
  return handler(ctx, req)
}

Register this interceptor alongside your auth interceptor (order matters—auth first, then authorization):

grpcServer := grpc.NewServer(
  grpc.UnaryInterceptor(grpc.ChainUnaryInterceptors(
    AuthInterceptor, // Parses JWT into context
    AuthorizationInterceptor, // Checks permissions
  )),
)

This way, you never have to write permission checks in your service methods again—just mark the required permissions in your proto, and the interceptor handles the rest.

2. Use Method Wrappers for Request-Specific Checks

If your permission logic depends on request data (like req.ID in your GetSomething example), a global interceptor might not be enough (since it can’t easily access request fields generically). Instead, use a decorator pattern to wrap your service methods with permission checks.

Example Wrapper for Your GetSomething Method

type MyServiceServer struct {
  pb.UnimplementedMyServiceServer
}

// Inner business logic with no permission checks
func (s *MyServiceServer) getSomethingInternal(ctx context.Context, req *pb.GetSomethingRequest) (*pb.GetSomethingResponse, error) {
  // Your core business logic here
  return &pb.GetSomethingResponse{}, nil
}

// Wrapped method that handles permission checks
func (s *MyServiceServer) GetSomething(ctx context.Context, req *pb.GetSomethingRequest) (*pb.GetSomethingResponse, error) {
  claims, ok := ctx.Value(ctxKey).(MyStruct)
  if !ok {
    return nil, status.Errorf(codes.Unauthenticated, "invalid authentication")
  }

  // Reuse your existing hasAccessTo logic
  if !hasAccessTo(ctx, req.ID) {
    return nil, status.Errorf(codes.PermissionDenied, "no access to resource %s", req.ID)
  }

  return s.getSomethingInternal(ctx, req)
}

For cleaner organization, you can even create a reusable wrapper function that takes your service method and a permission check function:

type GetSomethingHandler func(ctx context.Context, req *pb.GetSomethingRequest) (*pb.GetSomethingResponse, error)

func withResourceAccessCheck(handler GetSomethingHandler) GetSomethingHandler {
  return func(ctx context.Context, req *pb.GetSomethingRequest) (*pb.GetSomethingResponse, error) {
    claims, ok := ctx.Value(ctxKey).(MyStruct)
    if !ok {
      return nil, status.Errorf(codes.Unauthenticated, "invalid authentication")
    }
    if !hasAccessTo(ctx, req.ID) {
      return nil, status.Errorf(codes.PermissionDenied, "no access to resource %s", req.ID)
    }
    return handler(ctx, req)
  }
}

// Then use it in your server:
func (s *MyServiceServer) GetSomething(ctx context.Context, req *pb.GetSomethingRequest) (*pb.GetSomethingResponse, error) {
  return withResourceAccessCheck(s.getSomethingInternal)(ctx, req)
}

3. Code Generation for Large-Scale Services

If you have a lot of service methods with varying permission rules, consider using a protoc plugin to auto-generate the permission-check wrappers. You can define more detailed auth rules in your proto annotations (e.g., which resources a method can access, required roles), then the plugin generates the Go code that wraps your service methods with the necessary checks.

This is especially useful for large teams or services with complex auth requirements—it eliminates manual repetition entirely and ensures consistency across your codebase.


内容的提问来源于stack exchange,提问作者Sergii Getman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:26:06