При прямой загрузке в S3 браузер сначала может отправить preflight OPTIONS. Если origin, метод или заголовок не разрешены CORS-конфигурацией бакета, запрос блокируется в браузере, хотя presigned URL и права доступа формально корректны.

Сравните фактический Origin и Access-Control-Request-Headers с CORS-правилом. Не ставьте wildcard для всего подряд: разрешите только домены, методы и заголовки, необходимые вашему сценарию.

Что сделать в первую очередь

  • Сохраните ошибку Console и полный preflight во вкладке Network.
  • Запишите точный Origin страницы, включая схему и порт.
  • Проверьте метод загрузки PUT или POST и отправляемые заголовки.
  • Убедитесь, что presigned URL еще действует и создан для того же метода.

Почему возникает проблема

Симптом обычно появляется не из-за одной настройки. Сначала разделите путь данных на этапы и найдите место, где фактическое поведение расходится с ожидаемым.

  • AllowedOrigins не содержит рабочий домен или содержит неправильную схему.
  • AllowedHeaders не разрешает Content-Type, x-amz-* или пользовательский заголовок.
  • Подпись создана для PUT, а frontend отправляет POST.
  • CDN или reverse proxy кеширует preflight без учета Origin.

Пошаговая диагностика

  • Повторите OPTIONS и загрузку через curl с теми же заголовками.
  • Проверьте CORS-конфигурацию именно нужного бакета и endpoint.
  • Сравните SignedHeaders presigned URL с реальным запросом.
  • Отделите CORS-ошибку браузера от 403 политики S3.
  • Проверьте ответ через CDN и напрямую в object storage.

Как исправить

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

  • Разрешите точные production-origin и отдельный тестовый origin при необходимости.
  • Добавьте только используемые методы и заголовки.
  • Создавайте presigned URL для фактического метода, key и Content-Type.
  • Настройте кеш preflight с корректным Vary: Origin.
  • Оставьте бакет закрытым и выдавайте временные права через backend.

Как проверить результат

  • Preflight возвращает разрешенный origin, методы и заголовки.
  • Файл загружается, получает ожидаемый Content-Type и открывается по разрешенному сценарию.
  • Запрос с постороннего origin не получает доступ.

Как не допустить повторения

  • Храните CORS и bucket policy в управляемой конфигурации.
  • Тестируйте загрузку после смены домена, CDN или SDK.
  • Ограничивайте срок, размер и ключ presigned-загрузки.

Частые вопросы

Почему запрос работает в curl, но не в браузере?

CORS применяет браузер. Curl не блокирует ответ по политике origin, поэтому нужно отдельно проверять preflight.

Можно ли указать AllowedOrigins *?

Иногда технически можно, но для пользовательских файлов безопаснее точные origin и минимальные разрешения.

Когда стоит обратиться за помощью

Для разбора нужны URL endpoint без подписи, CORS-правило и обезличенные заголовки preflight. Полный presigned URL лучше не публиковать, пока он действует.

Итог

Рабочая загрузка в S3 сочетает точный CORS, временную подпись и закрытый бакет. Я могу настроить frontend, backend и object storage как единый безопасный поток.