Interceptor - middleware gRPC. В нём проверяют токен, пишут request ID и собирают метрики без копирования кода в каждый метод.

Unary interceptor

func authInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok || len(md.Get("authorization")) == 0 {
        return nil, status.Error(codes.Unauthenticated, "missing token")
    }
    if err := validate(md.Get("authorization")[0]); err != nil {
        return nil, status.Error(codes.Unauthenticated, "invalid token")
    }
    return handler(ctx, req)
}

Он вызывается для унарных методов и решает, вызывать ли handler.

Stream interceptor

Для потоков используют grpc.StreamServerInterceptor и обёртку grpc.ServerStream, потому что сообщения приходят постепенно. Проверка выполняется один раз до обработки, а не на каждом Recv.

Регистрируют перехватчики при создании сервера через grpc.UnaryInterceptor и grpc.StreamInterceptor.

Request ID

x-request-id из metadata добавляют в контекст логов. Не заменяют его случайным значением на каждом методе: корреляция должна совпадать по всей цепочке.

Публичный метод вроде health check можно исключить из auth через info.FullMethod, но список исключений явный и короткий.

Порядок и исключения

При нескольких interceptors порядок регистрации становится порядком исполнения. Сначала обычно ставят recovery, затем наблюдаемость, затем auth: паника не должна уйти мимо логов, а отклонённый запрос не должен попасть в обработчик.

запрос recovery метрики auth handler

Запрос проходит перехватчики в порядке регистрации. Ответ и ошибки идут обратно через ту же цепочку.

Health check пропускают без токена по точному info.FullMethod, а не по префиксу всех методов сервиса. Любое расширение списка исключений проверяют тестом: иначе наружу случайно откроют бизнес-метод.

Перехватчик не должен блокировать вызов дольше дедлайна. Проверку токена по сети делают с контекстом вызова, а не с context.Background.

Коротко

  • Unary interceptor для обычных вызовов, stream interceptor для потоков.
  • Auth проверяется один раз на входе, не на каждое сообщение.
  • Request ID берут из metadata и пробрасывают дальше.
  • Исключения auth ограничены явным списком методов.