如何区分并管控公共、内部及混合类型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注解 + 身份认证的组合方案:
- 在Protobuf里标记每个方法的访问级别;
- 入口层(比如网关或服务端Interceptor)统一处理身份认证,识别请求是内部服务还是客户端;
- 根据方法的注解和身份类型做权限校验,内部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
相关产品推荐
相关产品推荐

