CORS

Материал из llmdocs — справочник разработчика
CORS
Полное названиеCross-Origin Resource Sharing
СтандартWHATWG Fetch Standard (ранее рекомендация W3C, 2014)
Основаполитика одного источника (same-origin policy)
Источник (origin)схема + хост + порт
Ключевой заголовокAccess-Control-Allow-Origin
Предварительный запросметод OPTIONS (preflight)
Где действуеттолько в браузерах

CORS (англ. Cross-Origin Resource Sharing — совместное использование ресурсов между источниками) — механизм, с помощью которого сервер сообщает браузеру, каким сайтам с других адресов разрешено читать его ответы. По умолчанию браузер запрещает скрипту на странице https://app.example.com читать ответ от https://api.other.com — это политика одного источника (same-origin policy). CORS — способ контролируемо ослабить этот запрет с помощью специальных HTTP-заголовков.[1][2] Ошибка «blocked by CORS policy» в консоли браузера — одна из самых частых у веб-разработчиков.

Политика одного источника

Источник (origin) — сочетание схемы, хоста и порта.[3]

Адрес Тот же источник, что https://example.com?
https://example.com/page да
http://example.com нет — другая схема
https://api.example.com нет — другой хост
https://example.com:8443 нет — другой порт

Политика защищает пользователя: без неё любой открытый сайт мог бы скриптом отправить запрос в ваш интернет-банк — браузер приложил бы ваши cookie — и прочитать ответ. Важно, что ограничение касается чтения ответа скриптом; загрузку картинок, скриптов, отправку форм с других источников политика не запрещает (от подделки таких запросов защищаются иначе, см. CSRF).

Как работает CORS

Браузер добавляет к запросу между источниками заголовок Origin, а сервер в ответе указывает, разрешён ли этот источник:

GET /v1/items HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Content-Type: application/json

Если заголовка нет или значение не совпадает, браузер всё равно получает ответ, но не отдаёт его скрипту и пишет ошибку в консоль. Сервер при этом запрос уже выполнил — CORS не защищает сервер, а защищает данные пользователя в браузере.

Простые и предварительные запросы

Простые запросы — GET, HEAD или POST с ограниченным набором заголовков и типами тела text/plain, multipart/form-data, application/x-www-form-urlencoded — отправляются сразу. Они не дают ничего, чего нельзя было сделать обычной HTML-формой.

Всё остальное — PUT, DELETE, PATCH, тело application/json, заголовок Authorization — требует предварительного запроса (preflight): браузер сначала отправляет OPTIONS и спрашивает разрешения.[1]

OPTIONS /v1/items/42 HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 600

Access-Control-Max-Age разрешает браузеру кешировать ответ, чтобы не повторять предварительный запрос перед каждым вызовом.

Заголовки

Заголовок ответа Назначение
Access-Control-Allow-Origin разрешённый источник или *
Access-Control-Allow-Methods разрешённые методы (для preflight)
Access-Control-Allow-Headers разрешённые заголовки запроса
Access-Control-Allow-Credentials: true можно отправлять cookie и HTTP-аутентификацию
Access-Control-Expose-Headers какие заголовки ответа доступны скрипту
Access-Control-Max-Age время кеширования результата preflight, в секундах

Учётные данные. По умолчанию fetch не отправляет cookie на другие источники. Если клиент указал credentials: "include", сервер обязан ответить Access-Control-Allow-Credentials: true и конкретным источником — подстановочный * в этом случае запрещён.

Пример на сервере

# FastAPI
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_methods=["GET", "POST", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
    allow_credentials=True,
)

Готовые решения есть во всех фреймворках (см. FastAPI); заголовки можно добавлять и на уровне обратного прокси.

Частые ошибки

  • Отражение любого Origin в Access-Control-Allow-Origin вместе с Allow-Credentials: true — фактически отключает защиту: любой сайт сможет читать данные пользователя.[6] Проверяйте источник по точному списку.
  • Проверка по подстроке: example.com.evil.net проходит проверку «содержит example.com».
  • Разрешение источника null — его можно получить из песочницы iframe или локального файла.
  • Забытый Vary: Origin при динамическом ответе — CDN закеширует ответ для одного источника и отдаст другому.
  • Попытка «починить» CORS на клиенте — заголовки разрешений выставляет только сервер. Обход через mode: "no-cors" даёт непрозрачный ответ, который нельзя прочитать.

Частое и самое простое решение — избежать запросов между источниками вовсе: отдавать фронтенд и API с одного домена, проксируя /api на бэкенд.[5]

CORS — механизм браузеров. Серверные программы, curl и мобильные приложения его не применяют, поэтому он не заменяет аутентификацию API.

См. также

Источники

  1. WHATWG Fetch Standard — CORS protocol
  2. MDN — Cross-Origin Resource Sharing (CORS)
  3. MDN — Same-origin policy
  4. W3C — Cross-Origin Resource Sharing (Recommendation, 2014)
  5. OWASP — HTML5 Security Cheat Sheet: CORS
  6. PortSwigger Web Security Academy — CORS vulnerabilities