При прямой загрузке в 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 как единый безопасный поток.