Настройка CORS: безопасные заголовки и механизм preflight-запросов
#Web API #Fetch API #Веб-безопасностьДля кого эта статья:
- Веб-разработчики, занимающиеся созданием и настройкой веб-приложений
- Специалисты по безопасности, работающие с веб-приложениями и API
- Люди, интересующиеся современными технологиями и методами обеспечения безопасности интернета
Безопасность веб-приложений — поле вечной битвы между функциональностью и защитой. Механизм CORS, подобно стражу на границе между доменами, решает эту дилемму, предоставляя контролируемый доступ к ресурсам через домены. Но неправильная настройка CORS превращает его из защитника в ворота для атак. 🔐 Погрузимся в мир кросс-доменных запросов, разберем безопасные заголовки и механизм preflight-запросов, чтобы вы могли создавать интеграции, которые одновременно удобны и непроницаемы для злоумышленников.
Что такое CORS и почему он нужен веб-разработчикам
CORS (Cross-Origin Resource Sharing) — это HTTP-механизм, позволяющий веб-приложениям запрашивать ресурсы с доменов, отличных от домена, с которого было загружено приложение. По умолчанию, браузеры блокируют такие запросы в соответствии с политикой одного источника (Same-Origin Policy), которая является фундаментальным принципом веб-безопасности.
Александр Черняев, Lead Frontend Developer Когда я разрабатывал административную панель для крупного интернет-магазина, фронтенд размещался на поддомене admin.shop.ru, а API находился на api.shop.ru. Каждый запрос к API сопровождался ошибкой CORS. Это произошло потому, что хотя поддомены выглядят как часть одного домена, браузеры считают их разными источниками. Заказчик настаивал на такой архитектуре для обеспечения масштабирования и безопасности. Мне пришлось глубоко погрузиться в настройку CORS, чтобы обеспечить правильное взаимодействие между компонентами системы. После корректной настройки заголовков на сервере API, мы получили не только работающую систему, но и дополнительный слой защиты благодаря точной спецификации разрешенных источников и методов.
Без CORS создание современных веб-приложений было бы практически невозможным, поскольку большинство из них используют микросервисную архитектуру или интегрируются с внешними API.
Вот основные сценарии, где CORS жизненно необходим:
- Фронтенд-приложение взаимодействует с бэкенд API, расположенным на другом домене
- Веб-приложение загружает ресурсы (шрифты, изображения) с CDN
- Система использует сторонние сервисы (аналитика, платежные шлюзы)
- Микрофронтенды обмениваются данными между собой
- PWA (Progressive Web Applications) обращаются к внешним источникам данных
CORS реализует принцип "default-deny" — запрещено всё, что явно не разрешено. Это усиливает безопасность, позволяя серверам точно указывать, кто может получить доступ к их ресурсам. 🛡️
| Проблема до появления CORS | Решение с использованием CORS |
|---|---|
| Необходимость использования небезопасного JSONP | Стандартизированный механизм с поддержкой всех HTTP-методов |
| Отсутствие контроля доступа к API | Детальная настройка разрешений по доменам, методам и заголовкам |
| Сложные прокси-решения на стороне сервера | Простая конфигурация заголовков HTTP |
| Уязвимости в безопасности из-за обходных решений | Встроенные в браузеры механизмы безопасности |

Безопасные заголовки CORS: конфигурация и применение
Правильная настройка заголовков CORS — краеугольный камень безопасности кросс-доменного взаимодействия. Рассмотрим ключевые заголовки и их безопасную конфигурацию.
Основной заголовок CORS — Access-Control-Allow-Origin. Он указывает, какие домены могут получать ответы от сервера:
Access-Control-Allow-Origin: *— разрешает доступ с любого домена (наименее безопасно)Access-Control-Allow-Origin: https://trusted-site.com— разрешает доступ только с указанного домена (рекомендуемый подход)
Использование wildcard (*) оправдано только для общедоступных ресурсов без авторизации и чувствительных данных. Для API, требующих аутентификации, всегда указывайте конкретные домены. 🔒
Заголовок Access-Control-Allow-Methods определяет, какие HTTP-методы разрешены при обращении к ресурсу:
Access-Control-Allow-Methods: GET, POST, PUT
Следуя принципу наименьших привилегий, включайте только те методы, которые действительно необходимы для работы вашего API.
Access-Control-Allow-Headers указывает, какие HTTP-заголовки могут использоваться при запросе:
Access-Control-Allow-Headers: Content-Type, Authorization
Особенно важен заголовок Access-Control-Allow-Credentials, который разрешает передачу аутентификационных данных (cookies, заголовки авторизации) при кросс-доменных запросах:
Access-Control-Allow-Credentials: true
⚠️ Внимание: использование этого заголовка вместе с Access-Control-Allow-Origin: * недопустимо с точки зрения безопасности и блокируется браузерами.
Заголовок Access-Control-Expose-Headers позволяет клиентскому JavaScript-коду читать указанные заголовки из ответа сервера:
Access-Control-Expose-Headers: X-Custom-Header, Content-Length
Заголовок Access-Control-Max-Age определяет, как долго (в секундах) браузер может кэшировать результаты preflight-запроса:
Access-Control-Max-Age: 3600
Это оптимизирует производительность, уменьшая количество preflight-запросов, но помните, что длительное кэширование может затруднить обновление политик CORS.
| Заголовок CORS | Уровень безопасности | Рекомендуемая настройка |
|---|---|---|
| Access-Control-Allow-Origin: * | Низкий | Только для публичных, нечувствительных ресурсов |
| Access-Control-Allow-Origin: https://example.com | Высокий | Для API с аутентификацией |
| Access-Control-Allow-Methods: * | Низкий | Никогда не использовать, указывать конкретные методы |
| Access-Control-Allow-Credentials: true | Средний | Только с конкретными доменами в Allow-Origin |
| Access-Control-Max-Age: 86400 | Средний | Не более 3600 (1 час) для производственной среды |
Механизм preflight-запросов: процесс и обработка
Preflight-запросы — это дополнительные проверочные запросы, которые браузер автоматически отправляет перед "сложными" кросс-доменными запросами. Они используют HTTP-метод OPTIONS и позволяют серверу проверить, разрешен ли планируемый запрос согласно политике CORS. 🔍
Марина Соколова, Security Engineer На одном из проектов мы столкнулись с необъяснимой проблемой: API перестал работать после внедрения новой системы безопасности, хотя все заголовки CORS были настроены правильно. После долгого расследования выяснилось, что новый WAF (Web Application Firewall) блокировал OPTIONS-запросы как потенциально опасные. Это было классическое недопонимание механизма preflight: администраторы безопасности не осознавали, что OPTIONS — это легитимная часть работы веб-приложений. После настройки правил WAF для пропуска preflight-запросов система заработала. Этот случай научил меня всегда проверять полную цепочку обработки запросов, включая все промежуточные системы защиты.
Не все кросс-доменные запросы требуют preflight. "Простые запросы" выполняются сразу, если они соответствуют всем следующим критериям:
- Используют методы GET, HEAD или POST
- Имеют только разрешенные заголовки (Accept, Accept-Language, Content-Language, Content-Type)
- Если используется Content-Type, то только следующие значения: application/x-www-form-urlencoded, multipart/form-data или text/plain
- Не используют события ReadableStream
- Не используют XMLHttpRequestUpload с обработчиками событий
Все остальные запросы считаются "сложными" и требуют preflight. Рассмотрим процесс выполнения такого запроса:
- Браузер отправляет preflight-запрос OPTIONS на тот же URL, содержащий:
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://client-app.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Requested-With
- Сервер проверяет информацию и отвечает, разрешая или запрещая запрос:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://client-app.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Requested-With
Access-Control-Max-Age: 3600
- Если ответ положительный, браузер выполняет основной запрос. В противном случае возникает ошибка CORS.
Preflight-запросы часто вызывают проблемы у разработчиков, которые не понимают, почему возникают дополнительные OPTIONS-запросы. Важно помнить, что эти запросы:
- Не содержат тела запроса и не выполняют бизнес-операции
- Должны обрабатываться быстро, чтобы не замедлять работу приложения
- Могут кэшироваться браузером для повышения производительности
- Должны корректно обрабатываться промежуточными системами (прокси, WAF)
Оптимизация обработки preflight-запросов — важная часть настройки CORS. Установите разумное время кэширования через Access-Control-Max-Age, но не слишком большое, чтобы иметь возможность оперативно обновлять политики. 📈
Настройка CORS на различных серверных платформах
Реализация CORS варьируется в зависимости от серверной платформы. Рассмотрим настройку CORS на наиболее распространенных технологиях. 🛠️
Node.js с Express
Для Node.js популярным решением является middleware cors:
const express = require('express');
const cors = require('cors');
const app = express();
// Базовая настройка с разрешением всех источников (небезопасно)
app.use(cors());
// Безопасная конфигурация
const corsOptions = {
origin: 'https://trusted-app.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 3600
};
app.use(cors(corsOptions));
// Настройка для отдельных маршрутов
app.get('/api/public-data', cors(), (req, res) => {
res.json({ message: 'This is public data' });
});
app.get('/api/protected-data', cors(corsOptions), (req, res) => {
res.json({ message: 'This is protected data' });
});
ASP.NET Core
В ASP.NET Core CORS настраивается в файле Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("ProductionPolicy",
builder =>
{
builder.WithOrigins("https://trusted-app.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Content-Type", "Authorization")
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromSeconds(3600));
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Применение политики CORS
app.UseCors("ProductionPolicy");
// Остальная конфигурация...
}
PHP
В PHP заголовки CORS обычно устанавливаются напрямую:
<?php
// Проверка Origin
$allowedOrigins = array(
'https://trusted-app.com',
'https://staging-app.com'
);
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
if (in_array($origin, $allowedOrigins)) {
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: 3600');
}
// Обработка preflight-запросов
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
exit(0);
}
// Остальной код API...
?>
Nginx
Nginx может управлять CORS на уровне веб-сервера:
server {
listen 80;
server_name api.example.com;
location / {
# Основные заголовки CORS
add_header 'Access-Control-Allow-Origin' 'https://trusted-app.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' '3600';
# Обработка preflight-запросов
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://trusted-app.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' '3600';
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' '0';
return 204;
}
proxy_pass http://backend_server;
# Другие настройки прокси...
}
}
При разработке сложных систем часто используется многоуровневый подход, где CORS настраивается как на уровне веб-сервера (Nginx/Apache), так и на уровне приложения. Это обеспечивает дополнительную гибкость и безопасность. 🔧
Устранение типичных проблем CORS и проверка работоспособности
CORS-ошибки — одни из самых распространенных проблем при разработке веб-приложений. Разберем типичные проблемы и методы их диагностики. 🔍
Распространенные ошибки CORS и их решения:
- Ошибка: "Access to XMLHttpRequest has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header" — Сервер не возвращает заголовок Access-Control-Allow-Origin. Решение: добавить заголовок с корректным значением.
- Ошибка: "Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers" — Не настроен заголовок Access-Control-Allow-Headers. Решение: добавить нужные заголовки в Access-Control-Allow-Headers.
- Ошибка: "Method PUT is not allowed by Access-Control-Allow-Methods" — Не разрешен используемый HTTP-метод. Решение: добавить метод в Access-Control-Allow-Methods.
- Ошибка: "The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'" — Несовместимая комбинация заголовков. Решение: указать конкретный origin вместо wildcard при использовании credentials.
- Ошибка: "The 'Access-Control-Allow-Origin' header contains multiple values" — Отправляется несколько заголовков CORS. Решение: проверить middleware и убедиться, что заголовки не дублируются.
Инструменты для диагностики проблем CORS:
- Консоль разработчика браузера (F12) — основной инструмент для выявления ошибок CORS, отображает детальные сообщения об ошибках.
- Вкладка Network в инструментах разработчика — позволяет анализировать заголовки запросов и ответов.
- curl — для проверки заголовков на уровне HTTP без влияния браузера:
curl -X OPTIONS https://api.example.com/data -H "Origin: https://client-app.com" -H "Access-Control-Request-Method: POST" -v
- Расширения для браузера (CORS Unblock, Allow CORS) — временное решение для отладки, но не для продакшена.
- Online CORS-валидаторы — позволяют проверить корректность настройки CORS на вашем API.
Стратегия отладки CORS:
- Проверьте сообщение об ошибке — оно часто содержит точную причину проблемы.
- Проанализируйте заголовки ответа сервера — убедитесь, что все необходимые заголовки CORS присутствуют.
- Проверьте preflight-запросы — убедитесь, что сервер корректно отвечает на OPTIONS-запросы.
- Используйте временное решение с wildcard (*) — для быстрой проверки работоспособности (только для разработки).
- Проверьте промежуточное ПО — убедитесь, что прокси, балансировщики нагрузки и WAF не блокируют заголовки CORS.
Чеклист проверки корректной настройки CORS:
- Заголовок Access-Control-Allow-Origin содержит правильный домен (или список доменов)
- Все используемые HTTP-методы перечислены в Access-Control-Allow-Methods
- Все необходимые заголовки указаны в Access-Control-Allow-Headers
- При использовании cookies или авторизации установлен Access-Control-Allow-Credentials: true
- Заголовок Access-Control-Max-Age установлен с разумным значением
- Обработка OPTIONS-запросов работает корректно и быстро
Помните, что политика CORS применяется только браузерами — прямые запросы от серверных приложений не подчиняются ограничениям CORS. Это означает, что CORS — это механизм защиты клиента, а не сервера. Сервер должен иметь собственные механизмы авторизации и аутентификации. ⚠️
Правильная настройка CORS — это баланс между безопасностью и функциональностью. Чрезмерно строгие настройки могут привести к проблемам для ваших пользователей, а слишком свободные — создать уязвимости. Используйте принцип наименьших привилегий: разрешайте только то, что действительно необходимо. Регулярно аудируйте ваши настройки CORS, особенно при изменении архитектуры приложения или добавлении новых интеграций. Помните, что каждый разрешенный домен, метод или заголовок — это потенциальная точка атаки, поэтому каждое разрешение должно быть осознанным и обоснованным.
Тимур Голубев
веб-разработчик