CORS: руководство по Cross-Origin Resource Sharing
Cross-Origin Resource Sharing (CORS) — механизм безопасности, встроенный в веб-браузеры, который контролирует, как веб-страницы с одного источника (домен, протокол, проверку портов) могут запрашивать ресурсы с другого источника. Без CORS браузеры применяют политику одного источника (Same-Origin Policy, SOP), блокируя кросс-доменные HTTP-запросы из JavaScript. CORS предоставляет безопасный, стандартизированный способ ослабить это ограничение при необходимости.
Политика одного источника (Same-Origin Policy)
Политика одного источника — один из фундаментальных механизмов безопасности веба. Два URL имеют одинаковый источник, если совпадают протокол, домен и порт:
| URL A | URL B | Один источник? | Причина |
|---|---|---|---|
| SSL/TLS проверку://example.com/page1 | https://example.com/page2 | Да | Одинаковые протокол, домен, порт |
| https://example.com | http://example.com | Нет | Разный протокол |
| https://example.com | https://API документацию.example.com | Нет | Другой субдомен |
| https://example.com | https://example.com:8080 | Нет | Другой порт |
| https://example.com | https://other.com | Нет | Другой домен |
Без политики одного источника любой сайт мог бы читать данные из вашего банковского приложения, почты или соцсетей через JavaScript — при условии, что вы авторизованы.
Как работает CORS
CORS работает через проверку HTTP-заголовков. При кросс-доменном запросе браузер включает заголовок Origin. Сервер отвечает заголовками Access-Control-*, указывая, разрешён ли запрос:
# Браузер отправляет:
GET /api/data 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
Content-Type: application/json
{"data": "response"}
Если сервер не включает соответствующие CORS-заголовки, браузер блокирует ответ и логирует ошибку CORS в консоли.
Простые запросы и preflight-запросы
Браузер разделяет кросс-доменные запросы на два типа:
Простые запросы
Запрос считается «простым», если выполнены все условия:
- Метод — GET, HEAD или POST
- Используются только «безопасные» заголовки: Accept, Accept-Language, Content-Language, Content-Type
- Content-Type — один из: application/x-www-form-urlencoded, multipart/form-data, text/plain
Простые запросы отправляются напрямую. Браузер проверяет заголовки ответа постфактум.
Preflight-запросы
Любой запрос, не являющийся «простым», запускает preflight — автоматический OPTIONS-запрос перед фактическим запросом:
# Шаг 1: Браузер отправляет preflight
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
# Шаг 2: Сервер отвечает на preflight
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
# Шаг 3: Браузер отправляет фактический запрос
PUT /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...
{"key": "value"}
Справочник по заголовкам CORS
| Заголовок | Направление | Назначение |
|---|---|---|
Origin | Запрос | Сообщает серверу, откуда пришёл запрос |
Access-Control-Allow-Origin | Ответ | Какие источники могут обращаться к ресурсу |
Access-Control-Allow-Methods | Ответ | Разрешённые HTTP-методы для preflight |
Access-Control-Allow-Headers | Ответ | Разрешённые заголовки запроса для preflight |
Access-Control-Allow-Credentials | Ответ | Разрешены ли cookies/заголовки авторизации |
Access-Control-Expose-Headers | Ответ | Заголовки ответа, доступные через JS |
Access-Control-Max-Age | Ответ | Время кэширования результата preflight (секунды) |
Access-Control-Request-Method | Запрос | Метод фактического запроса (preflight) |
Access-Control-Request-Headers | Запрос | Заголовки фактического запроса (preflight) |
Типичные конфигурации CORS
Разрешить конкретный источник
# Nginx
location /api/ {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 86400 always;
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend;
}
Разрешить несколько источников (динамически)
# Nginx — проверка Origin по списку
map $http_origin $cors_origin {
default "";
"https://app.example.com" $http_origin;
"https://admin.example.com" $http_origin;
"https://staging.example.com" $http_origin;
}
server {
location /api/ {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
}
}
Обработчик CORS в PHP
$allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
header('Vary: Origin');
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
CORS с учётными данными
Когда запросы включают cookies или заголовки авторизации, действуют дополнительные правила:
Access-Control-Allow-Credentials: trueдолжен быть установленAccess-Control-Allow-Originне может быть*— должен быть конкретный источник- На клиенте нужно указать
credentials: 'include'в опциях fetch
// Клиентский JavaScript
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // Отправлять cookies кросс-доменно
headers: {
'Authorization': 'Bearer token123'
}
});
Частые ошибки CORS и решения
| Ошибка | Причина | Решение |
|---|---|---|
| No 'Access-Control-Allow-Origin' header | Сервер не отправляет CORS-заголовки | Добавить CORS-заголовки в ответ сервера |
| Origin not allowed | Сервер разрешает другие источники | Добавить ваш источник в список разрешённых |
| Preflight response not successful | OPTIONS-запрос возвращает ошибку | Обработать метод OPTIONS, вернуть 204 |
| Credentials с Allow-Origin: * | Wildcard запрещён с credentials | Указать конкретный источник вместо * |
| Request header not allowed | Заголовок не в Allow-Headers | Добавить заголовок в Access-Control-Allow-Headers |
Лучшие практики безопасности
- Никогда не используйте
Access-Control-Allow-Origin: *для защищённых эндпоинтов — это позволяет любому сайту делать авторизованные запросы - Валидируйте заголовок Origin на сервере — не отражайте его слепо
- Ограничивайте разрешённые методы — разрешайте только те, что API реально использует
- Ограничивайте раскрываемые заголовки — только те, что реально нужны клиенту
- Устанавливайте Access-Control-Max-Age — уменьшает частоту preflight (86400 = 24 часа)
- Используйте Vary: Origin — при динамическом выборе источников, чтобы кэши различали ответы
CORS и мониторинг
Для веб-мониторинг сайтов CORS влияет на взаимодействие инструментов с API:
- Браузерный мониторинг (Real User Monitoring) подчиняется ограничениям CORS
- Серверный мониторинг не затронут CORS — это механизм только для браузеров
- При тестировании API из браузера ошибки CORS могут маскировать реальные серверные ошибки
- Кэширование preflight (Max-Age) влияет на скорость применения изменений CORS-политики
Итоги
CORS — механизм безопасности браузера, контролирующий кросс-доменные HTTP-запросы. Он использует HTTP-заголовки для определения, может ли веб-страница с одного источника обращаться к ресурсам с другого. Простые запросы отправляются напрямую; сложные запросы запускают preflight OPTIONS-проверку. Правильная настройка CORS требует указания разрешённых источников, методов и заголовков — с особой осторожностью при запросах с учётными данными. Понимание CORS необходимо всем, кто создаёт или использует веб-API, так как неправильная конфигурация ведёт либо к уязвимостям, либо к нерабочему функционалу.
Проверьте ваш сайт прямо сейчас
Проверить →