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: паника не должна уйти мимо логов, а отклонённый запрос не должен попасть в обработчик.
Запрос проходит перехватчики в порядке регистрации. Ответ и ошибки идут обратно через ту же цепочку.
Health check пропускают без токена по точному info.FullMethod, а не по префиксу всех методов сервиса. Любое расширение списка исключений проверяют тестом: иначе наружу случайно откроют бизнес-метод.
Перехватчик не должен блокировать вызов дольше дедлайна. Проверку токена по сети делают с контекстом вызова, а не с context.Background.
Коротко
- Unary interceptor для обычных вызовов, stream interceptor для потоков.
- Auth проверяется один раз на входе, не на каждое сообщение.
- Request ID берут из metadata и пробрасывают дальше.
- Исключения auth ограничены явным списком методов.