Настройка CORS: безопасные заголовки и механизм preflight-запросов
Перейти

Настройка CORS: безопасные заголовки и механизм preflight-запросов

#Web API  #Fetch API  #Веб-безопасность  
Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Веб-разработчики, занимающиеся созданием и настройкой веб-приложений
  • Специалисты по безопасности, работающие с веб-приложениями и 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. Рассмотрим процесс выполнения такого запроса:

  1. Браузер отправляет 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

  1. Сервер проверяет информацию и отвечает, разрешая или запрещая запрос:
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

  1. Если ответ положительный, браузер выполняет основной запрос. В противном случае возникает ошибка 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:

  1. Консоль разработчика браузера (F12) — основной инструмент для выявления ошибок CORS, отображает детальные сообщения об ошибках.
  2. Вкладка Network в инструментах разработчика — позволяет анализировать заголовки запросов и ответов.
  3. curl — для проверки заголовков на уровне HTTP без влияния браузера:
curl -X OPTIONS https://api.example.com/data -H "Origin: https://client-app.com" -H "Access-Control-Request-Method: POST" -v

  1. Расширения для браузера (CORS Unblock, Allow CORS) — временное решение для отладки, но не для продакшена.
  2. Online CORS-валидаторы — позволяют проверить корректность настройки CORS на вашем API.

Стратегия отладки CORS:

  1. Проверьте сообщение об ошибке — оно часто содержит точную причину проблемы.
  2. Проанализируйте заголовки ответа сервера — убедитесь, что все необходимые заголовки CORS присутствуют.
  3. Проверьте preflight-запросы — убедитесь, что сервер корректно отвечает на OPTIONS-запросы.
  4. Используйте временное решение с wildcard (*) — для быстрой проверки работоспособности (только для разработки).
  5. Проверьте промежуточное ПО — убедитесь, что прокси, балансировщики нагрузки и 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, особенно при изменении архитектуры приложения или добавлении новых интеграций. Помните, что каждый разрешенный домен, метод или заголовок — это потенциальная точка атаки, поэтому каждое разрешение должно быть осознанным и обоснованным.

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Что такое CORS-safelisted response header?
1 / 5

Тимур Голубев

веб-разработчик

Свежие материалы

Загрузка...