Валидация и сериализация данных
Валидация на сервере это не про удобство, а про безопасность: клиент может слать в API что угодно в обход твоего UI. Собес проверяет, понимаешь ли ты, что сервер - единственная надёжная граница доверия, и знаешь ли классическую дыру mass assignment.
Типовые формулировки: «зачем валидировать на сервере, если фронт уже проверил?», «что такое mass assignment?», «почему разные модели на вход и выход».
Сервер - единственная граница доверия
Вход валидируют на сервере, потому что клиент - браузер, curl, чужой скрипт - шлёт что угодно напрямую в API, минуя твою форму и её проверки. Клиентская валидация нужна для UX, но как защита она бесполезна: её обходят одним прямым запросом. Надёжная граница ровно одна - сервер.
// Отсюда правило: любое поле, от которого зависит логика или безопасность, перепроверяется на бэкенде, каким бы «проверенным» оно ни пришло с фронта.
- серверная валидация
- единственная надёжная граница доверия к данным
- mass assignment
- уязвимость слепого маппинга лишних полей тела
Mass assignment и раздельные схемы
Слепой маппинг всего тела запроса в модель это mass assignment: клиент добавит в JSON is_admin или is_verified, и если схема принимает всё подряд, эти поля проставятся. Защита - принимать только явно разрешённые поля через схему-белый-список, а не биндить сущность целиком.
И разделяй вход с выходом: на входе есть password и нет id, на выходе есть id и created_at, но нет password. Одна модель на оба направления рано или поздно протечёт - пароль просочится наружу или лишнее пролезет внутрь.
// В FastAPI это ровно UserCreate (вход) и UserOut (выход, он же response_model), в DRF (Django REST Framework) - разные сериализаторы или поля read_only.
- schema in/out
- разные модели приёма и отдачи
Как отвечать: «Фронт уже проверил форму - зачем валидировать на сервере?»
Потому что клиентская проверка защищает UX, а не данные. Любой может отправить запрос напрямую в API - через curl, Postman или свой скрипт - в обход браузера и всей фронтовой валидации. Значит фронт легко обойти, и единственная надёжная граница доверия - сервер. Особенно это критично для полей вроде роли или флага is_admin: если слепо замапить всё тело в модель, клиент их себе и проставит это mass assignment. Поэтому на бэкенде я принимаю только белый список полей через схему и перепроверяю всё, от чего зависят логика и права.
Названа суть (фронт про UX, обходится напрямую), приведена конкретная атака mass assignment и защита белым списком - ответ безопасника, а не «так положено».
На чём валят
- −Доверять клиентской валидации: её легко обойти прямым запросом к API.
- −Принимать лишние поля тела в модель - клиент проставит is_admin (mass assignment).
- −Одна модель на вход и выход: пароль просочится наружу или лишнее пролезет внутрь.
- −Валидировать формат, но не бизнес-правила (диапазоны, права на объект) - дыра остаётся.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.
- Клиент прислал тело с лишним полем is_admin, которого нет в схеме. Правильное поведение?A)Сохранить поле как есть — вдруг оно понадобится приложению позжеB)Игнорировать или отклонить — не маппить вслепую в модельC)Вернуть 500, ведь наличие лишнего поля — это сбой сервераD)Автоматически завести новую колонку в таблице под это поле
показать ответ и разбор
+B)Игнорировать или отклонить — не маппить вслепую в модель// разбор: Слепой маппинг всего тела в модель — уязвимость mass assignment: клиент проставит поля, которые не должен (is_admin, is_verified). Схема ввода должна принимать только разрешённые поля, а лишние игнорировать или отклонять. Pydantic/DRF-сериализаторы для этого и берут белый список полей.
- Зачем разделять входную и выходную модели (schema in / schema out)?A)Вход и выход имеют разные поля — пароль внутрь, но не наружуB)Выходная модель обязана буквально повторять входную один в одинC)Разделение нужно только чтобы удвоить объём кода на ревьюD)Это требование фреймворка, иначе эндпоинт вообще не запустится
показать ответ и разбор
+A)Вход и выход имеют разные поля — пароль внутрь, но не наружу// разбор: Приём и отдача — разные контракты: на входе есть password и нет id, на выходе есть id/created_at и нет password. Отдельные модели (UserCreate vs UserOut) не дают пароль утечь наружу и лишним полям — попасть внутрь. Одна модель на оба направления рано или поздно протечёт.
- Что такое сериализация в контексте API?A)Превращение объекта в формат для передачи (JSON) и обратноB)Сжатие тела ответа для уменьшения объёма передаваемого по сети трафикаC)Проверка входных данных на соответствие ограничениям и бизнес-правилам приложенияD)Шифрование данных перед отправкой их по сети клиенту или другому сервису
показать ответ и разбор
+A)Превращение объекта в формат для передачи (JSON) и обратно// разбор: Сериализация — преобразование объекта в представление для передачи/хранения (например, Python-объект → JSON), десериализация — обратно. Отдельно стоит валидация: проверка, что данные корректны. Библиотеки вроде Pydantic делают и то и другое: разбирают вход, валидируют, отдают типизированный объект, а на выходе сериализуют его назад.
- Почему валидацию входных данных делают на границе приложения, а не в глубине логики?A)Граница — место, где удобнее всего перехватить сырые данные запросаB)Дальше по коду данные считаются уже корректными — меньше проверок и баговC)Так валидация выполняется быстрее, потому что граница ближе к сетевому сокетуD)В глубине логики валидация недоступна — данные туда не доходят
показать ответ и разбор
+B)Дальше по коду данные считаются уже корректными — меньше проверок и багов// разбор: Валидация на входе (в схеме запроса) создаёт чёткий рубеж: за ним данные уже типизированы и корректны, и внутренний код не засоряется повторными проверками «а вдруг тут не число». Это уменьшает дублирование, локализует ошибки ввода в одном месте и делает 422-ответы единообразными. Размазанная по коду валидация — источник дыр и багов.
- Клиент прислал
"42"строкой, а поле объявлено int. Что разумно сделать схеме?A)Зависит от режима: строгий отвергнет, мягкий приведёт "42" к 42B)Отвергать: строку в целочисленное поле принимать недопустимоC)Принять как есть и хранить строкой, проигнорировав объявленный тип поляD)Молча приводить строку к числу, даже заведомо нечисловуюпоказать ответ и разбор
+A)Зависит от режима: строгий отвергнет, мягкий приведёт "42" к 42// разбор: Есть два подхода. Коэрсинг (мягкий, дефолт Pydantic для многих типов): числовую строку '42' приведут к 42 — удобно для форм и query. Strict-режим: типы должны совпадать точно, '42' для int отвергается. Выбор зависит от источника данных и требований. Важно осознанно задать политику, а не полагаться на случай — иначе тихие приведения дадут сюрпризы.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.