Go gRPC 拦截器实战:在一元拦截器里统一做鉴权、日志与超时

test12026-09-206 次阅读

不想每个 RPC 都写一遍鉴权?交给拦截器

gRPC 的拦截器(interceptor)相当于服务端的全局中间件。一元拦截器签名统一,能在真正调用业务 handler 之前做鉴权、日志、超时、限流,之后又能统一收口响应与耗时。用 ChainUnaryInterceptor 把多个拦截器串成一条链,顺序即执行顺序。

一元拦截器:鉴权 + 链路日志

下面用两个拦截器演示:authInterceptor 从 metadata 取 token 校验,不合法直接返回 Unauthenticated;logInterceptor 包住 handler 记录方法名与耗时。两者通过 ctx 透传用户信息。

package main

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	md, _ := metadata.FromIncomingContext(ctx)
	tokens := md.Get("authorization")
	if len(tokens) == 0 || tokens[0] != "Bearer secret" {
		return nil, status.Error(codes.Unauthenticated, "缺少合法凭证")
	}
	ctx = context.WithValue(ctx, "user", tokens[0])
	return handler(ctx, req)
}

func logInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	start := time.Now()
	resp, err := handler(ctx, req)
	log.Printf("method=%s duration=%s err=%v", info.FullMethod, time.Since(start), err)
	return resp, err
}

func main() {
	s := grpc.NewServer(
		grpc.ChainUnaryInterceptor(authInterceptor, logInterceptor),
	)
	// pb.RegisterDemoServer(s, &server{})
	// s.Serve(lis)
	_ = s
}

把超时和取消也收进拦截器

拦截器里可以给 ctx 加超时,业务 handler 通过 select 监听 ctx.Done() 即可感知取消,避免慢请求拖垮整个服务。下面是一段 handler 侧的取消感知示例。

func (s *server) GetOrder(ctx context.Context, req *pb.OrderReq) (*pb.Order, error) {
	select {
	case <-time.After(50 * time.Millisecond):
		return &pb.Order{Id: req.Id}, nil
	case <-ctx.Done():
		return nil, status.Error(codes.DeadlineExceeded, ctx.Err().Error())
	}
}

工程收益

  • 鉴权、日志、超时等横切逻辑只写一次,业务 handler 保持纯净。
  • 拦截器链顺序可控,新增能力(限流、trace)只是再串一个函数。
  • 错误信息通过 status 统一返回,客户端拿到标准 gRPC 状态码。
T

test1

文章作者

不想每个 RPC 都写一遍鉴权?交给拦截器 gRPC 的拦截器(interceptor)相当于服务端的全局中间件。一元...

分类
技术
发布时间
2026-09-20
字数
约 1973 字
阅读
6 次