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

如何区分并管控公共、内部及混合类型gRPC API的访问权限?

移动端直连gRPC服务时的API权限区分方案

先聊聊你现有三个方案的实际落地细节:

  • Interceptor上下文标记:这是最轻量化的方案,但要注意标记的安全性——不能让客户端伪造这个标记,最好是在网关或者入口层就识别请求来源(比如内部服务用mTLS,客户端用JWT),把身份信息注入上下文,而不是让请求自己携带标记。
  • gRPC网关服务:相当于把原GraphQL层换成gRPC实现,本质是做一层请求转发和校验,适合需要对客户端请求做统一处理(比如限流、日志、参数转换)的场景,比手写GraphQL样板代码高效很多。
  • 组织层面拆分服务:如果内部和公共API的业务逻辑差异很大,拆分是合理的,但混合API的重复实现确实是痛点,可以通过抽取公共逻辑到内部共享库来减少代码冗余。

你没提到的其他可行方案

1. Protobuf方法级权限注解

在Protobuf定义里给每个方法添加自定义注解,标记它的访问级别:

syntax = "proto3";

import "google/protobuf/descriptor.proto";

extend google.protobuf.MethodOptions {
  AccessLevel access_level = 50001;
}

enum AccessLevel {
  INTERNAL = 0;
  PUBLIC = 1;
  HYBRID = 2;
}

service SmsService {
  rpc SendTwilioSms(SendSmsRequest) returns (SendSmsResponse) {
    option (access_level) = INTERNAL;
  }
}

service AuthService {
  rpc Login(LoginRequest) returns (LoginResponse) {
    option (access_level) = PUBLIC;
  }
}

service InboxService {
  rpc MarkAsRead(MarkAsReadRequest) returns (MarkAsReadResponse) {
    option (access_level) = HYBRID;
  }
}

然后用protoc插件生成权限校验代码,或者在Interceptor里通过反射读取方法的注解,结合请求身份(内部mTLS/客户端JWT)做校验。混合API可以在注解里标记需要执行的差异化逻辑,比如外部调用需校验用户权限,内部调用跳过。

2. 身份认证与授权规则绑定

给不同类型的请求分配不同的身份凭证:

  • 内部服务之间用mTLS证书认证,证书里携带服务标识;
  • 移动端用JWT令牌,令牌里携带用户ID和权限范围;
    服务端针对每个API方法配置授权规则:
  • 内部API:只允许携带有效内部服务证书的请求访问;
  • 公共API:只允许携带有效客户端JWT的请求访问;
  • 混合API:同时允许两种身份,但根据身份类型执行不同逻辑(比如内部调用不需要校验用户对收件箱项的所有权,外部调用必须校验)。
    可以用OPA(Open Policy Agent)把授权规则抽离成配置文件,不用硬编码在代码里,方便快速调整。

3. 服务拆分+内部路由

把公共API和内部API拆分到不同的Protobuf服务中,比如PublicInboxService和InternalInboxService,混合API可以在两个服务中都暴露,但内部服务版本跳过部分校验。然后通过服务网格(比如Istio)或者内部路由规则,限制移动端只能访问*PublicService,内部服务可以访问所有服务。这种方式适合内部和公共API的逻辑差异较大的场景,能做到物理隔离。

实际项目中的常用组合

大部分团队会采用Interceptor + Protobuf注解 + 身份认证的组合方案:

  1. 在Protobuf里标记每个方法的访问级别;
  2. 入口层(比如网关或服务端Interceptor)统一处理身份认证,识别请求是内部服务还是客户端;
  3. 根据方法的注解和身份类型做权限校验,内部API直接拦截非内部请求,混合API则在方法内根据身份执行不同逻辑。

比如更新收件箱已读状态的代码示例:

func (s *InboxService) MarkAsRead(ctx context.Context, req *MarkAsReadRequest) (*MarkAsReadResponse, error) {
    // 从上下文获取身份类型:内部服务/客户端用户
    authInfo, ok := peer.FromContext(ctx)
    isInternal := ok && authInfo.AuthType == "tls" && s.isInternalServiceCert(authInfo.AuthCredentials)
    
    if !isInternal {
        // 客户端调用:校验用户是否拥有该收件箱项
        if !s.userRepo.OwnsInboxItem(req.UserId, req.InboxItemId) {
            return nil, status.Errorf(codes.PermissionDenied, "无权修改该收件箱项")
        }
    }
    
    // 执行更新逻辑
    if err := s.inboxRepo.MarkAsRead(req.InboxItemId); err != nil {
        return nil, status.Errorf(codes.Internal, "更新失败: %v", err)
    }
    return &MarkAsReadResponse{Success: true}, nil
}

内容的提问来源于stack exchange,提问作者Logan Shire

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 23:22:16