CORS: как настроить кросс-доменные запросы для веб-разработки
Для кого эта статья:
- Веб-разработчики, работающие с API и межсайтовыми запросами
- Специалисты, занимающиеся конфигурацией серверов и безопасностью веб-приложений
Студенты и начинающие разработчики, интересующиеся углубленным изучением механик CORS
Каждый веб-разработчик хотя бы раз сталкивался с загадочной ошибкой CORS в консоли браузера, которая блокировала доступ к желанным данным. Эта ошибка появляется как неумолимый страж, преграждающий путь вашему фронтенду к API на другом домене. "Access-Control-Allow-Origin" — фраза, от которой у многих разработчиков начинается нервный тик. В этом руководстве я раскрою все тайны CORS, объясню принципы его работы и предоставлю четкие инструкции по настройке на различных серверах. Вооружившись этими знаниями, вы превратите CORS из врага в надежного союзника безопасности ваших приложений. 🛡️
Хотите раз и навсегда разобраться с CORS и другими нюансами веб-разработки? Курс Обучение веб-разработке от Skypro погружает вас в реальные проекты, где вы не только изучите механизмы безопасности веб-приложений, но и научитесь настраивать серверы под различные сценарии. Наши студенты уже на втором месяце обучения самостоятельно настраивают CORS-политики для своих проектов и API. Присоединяйтесь к команде профессионалов, которые решают, а не избегают сложных задач!
CORS: механизм безопасности для межсайтовых запросов
Cross-Origin Resource Sharing (CORS) — это механизм безопасности, реализованный в браузерах, который контролирует, как веб-страницы на одном домене могут запрашивать ресурсы с другого домена. По умолчанию браузеры блокируют такие "межсайтовые" запросы в соответствии с принципом Same-Origin Policy (политика одного источника), который является фундаментальным для безопасности в вебе.
Простыми словами, если JavaScript на сайте example.com пытается запросить данные с api.anotherservice.com, браузер блокирует такой запрос, если сервер api.anotherservice.com явно не разрешил такой доступ через специальные HTTP-заголовки.
| Термин | Определение | Пример |
|---|---|---|
| Источник (Origin) | Комбинация протокола, домена и порта | https://example.com:443 |
| Same-Origin Policy | Политика безопасности, ограничивающая доступ между разными источниками | Скрипт с example.com не может читать данные с api.com |
| Preflight-запрос | Предварительный запрос OPTIONS для проверки разрешений | Браузер отправляет OPTIONS перед POST на другой домен |
Когда браузер выполняет межсайтовый запрос, происходит следующее:
- Браузер добавляет к запросу заголовок
Origin, указывающий, откуда пришел запрос - Сервер проверяет этот заголовок и решает, разрешить доступ или нет
- Если доступ разрешен, сервер добавляет к ответу заголовок
Access-Control-Allow-Origin - Браузер проверяет этот заголовок и либо разрешает доступ к ответу, либо блокирует его
Для сложных запросов (с нестандартными HTTP-методами или заголовками) браузер сначала отправляет так называемый "preflight" запрос методом OPTIONS, чтобы узнать, разрешен ли такой запрос вообще.
Алексей Морозов, DevOps-инженер Помню, как-то раз получил срочный звонок от команды разработки в пятницу вечером. Они только что развернули новый фронтенд на отдельном домене, а API остался на старом. Все тесты на локалке проходили идеально, а в проде — сплошные ошибки CORS. "Алексей, мы все перепробовали, ничего не работает!" — паниковал лид фронтенда. Оказалось, что на тестовой среде у них был настроен прокси, который маскировал проблему CORS. А в боевой среде этот механизм отсутствовал. Я быстро добавил несколько строк в конфигурацию Nginx:
add_header 'Access-Control-Allow-Origin' 'https://new-frontend.example.org'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';Перезапустил сервер, и проблема моментально исчезла. С тех пор у нас в чеклисте деплоя отдельный пункт по проверке CORS-настроек. Сэкономили компании выходные и нервы клиентов.

Почему возникают ошибки CORS и как их диагностировать
Ошибки CORS обычно возникают, когда фронтенд на одном домене пытается взаимодействовать с API на другом домене, а сервер API не настроен для обработки таких межсайтовых запросов. 🔍
Типичные сценарии появления CORS-ошибок:
- Разработка фронтенда на localhost, который обращается к удаленному API
- Микросервисная архитектура с разными доменами для разных сервисов
- Интеграция со сторонними API
- Разделение фронтенда и бэкенда на разные поддомены
- Использование CDN для раздачи статического контента
Когда возникает CORS-ошибка, браузер обычно выдает сообщение в консоли разработчика, похожее на это:
Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Для диагностики CORS-ошибок следуйте этим шагам:
- Откройте консоль браузера и найдите точное сообщение об ошибке
- Используйте вкладку Network в инструментах разработчика, чтобы проверить заголовки запроса и ответа
- Проверьте, отправляется ли preflight-запрос (метод OPTIONS) и какой ответ приходит
- Убедитесь, что сервер правильно настроен для обработки CORS
Часто бывает полезно использовать инструменты вроде curl для тестирования заголовков API без влияния браузера:
curl -I -X OPTIONS https://api.example.com/data -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: GET"
Мария Соколова, Frontend Lead В нашем проекте мы столкнулись с интересной проблемой CORS. Разрабатывали SPA-приложение с авторизацией через JWT-токены. Фронтенд жил на своём домене, API — на другом. Базовые запросы работали отлично — мы настроили CORS на бэкенде. Но когда дело дошло до авторизованных запросов с токеном в заголовке Authorization, всё сломалось. Наш джуниор-разработчик потратил два дня, пытаясь понять, почему один и тот же эндпоинт работает без авторизации и не работает с ней. Я вмешалась, когда услышала разговоры о "мистических багах". Проблема оказалась в том, что запросы с нестандартными заголовками (в нашем случае Authorization) требуют дополнительной настройки CORS. Нужно было добавить:
Access-Control-Allow-Headers: AuthorizationА также корректно настроить обработку OPTIONS-запросов на сервере. После этих изменений всё заработало как по маслу. Этот случай стал отличным обучающим моментом для всей команды — теперь мы гораздо лучше понимаем, как работает CORS на разных уровнях сложности запросов.
Настройка CORS на серверах Node.js и Express
Настройка CORS в Node.js-приложениях, особенно использующих фреймворк Express, достаточно проста благодаря наличию специализированных пакетов. Рассмотрим наиболее распространенные подходы. ⚙️
Самый популярный способ — использование пакета cors:
npm install cors
Базовая настройка для разрешения всех источников (не рекомендуется для production):
const express = require('express');
const cors = require('cors');
const app = express();
// Разрешает CORS для всех запросов
app.use(cors());
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Более безопасная конфигурация с указанием конкретных доменов:
const corsOptions = {
origin: 'https://your-allowed-domain.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // Разрешает передачу cookies
maxAge: 86400 // Кэширование preflight-запросов на 24 часа
};
app.use(cors(corsOptions));
Для большей гибкости можно разрешить несколько доменов:
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://production.example.com',
'https://staging.example.com',
'http://localhost:8080'
];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('CORS policy violation'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
};
Если вы хотите применить разные CORS-политики для разных маршрутов:
// Глобальные настройки по умолчанию
app.use(cors({ origin: 'https://main-site.com' }));
// Специальная настройка для конкретного маршрута
app.get('/api/public-data', cors({ origin: '*' }), (req, res) => {
res.json({ message: 'This is public data' });
});
// Настройка для маршрутов API
const apiCorsOptions = {
origin: 'https://dashboard.example.com',
credentials: true
};
app.use('/api/admin', cors(apiCorsOptions), adminRoutes);
Для решения распространенных проблем с preflight-запросами:
// Явная обработка OPTIONS-запросов
app.options('*', cors(corsOptions));
// Или для конкретного маршрута
app.options('/api/sensitive-data', cors(specificOptions));
| Параметр cors | Описание | Пример значения |
|---|---|---|
| origin | Разрешённые домены для запросов | 'https://example.com' или ['site1.com', 'site2.com'] или функция |
| methods | Разрешённые HTTP-методы | ['GET', 'POST', 'PUT', 'DELETE'] |
| allowedHeaders | Разрешённые HTTP-заголовки | ['Content-Type', 'Authorization', 'X-API-Key'] |
| exposedHeaders | Заголовки, доступные клиенту | ['Content-Length', 'X-Rate-Limit'] |
| credentials | Разрешить отправку cookie | true или false |
| maxAge | Время кэширования preflight-ответов (в секундах) | 86400 (24 часа) |
Конфигурация CORS для Apache, Nginx и IIS
Настройка CORS на уровне веб-сервера — часто более эффективное решение, особенно при обслуживании множества приложений. Рассмотрим, как настроить CORS на популярных серверах. 🖥️
Настройка CORS в Apache
В Apache настройка осуществляется через модуль mod_headers. Добавьте следующие строки в файл .htaccess или в конфигурацию виртуального хоста:
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://trusted-site.com"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"
Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
Header set Access-Control-Allow-Credentials "true"
# Обработка OPTIONS-запросов
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>
Для разрешения нескольких доменов, используйте условную логику:
<IfModule mod_headers.c>
SetEnvIfNoCase Origin "^(https://site1\.com|https://site2\.com)$" ALLOWED_ORIGIN=$0
Header set Access-Control-Allow-Origin "%{ALLOWED_ORIGIN}e" env=ALLOWED_ORIGIN
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
Header set Access-Control-Allow-Credentials "true"
</IfModule>
Настройка CORS в Nginx
В Nginx настройка CORS выполняется в блоке server или location:
server {
listen 80;
server_name api.example.com;
location / {
# Базовые заголовки CORS
add_header 'Access-Control-Allow-Origin' 'https://trusted-site.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
# Обработка preflight-запросов
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
# Проксирование к основному приложению
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Для более сложных сценариев можно использовать переменные и условия:
map $http_origin $cors_origin {
default "";
"https://main-site.com" $http_origin;
"https://admin.example.com" $http_origin;
"http://localhost:8080" $http_origin;
}
server {
# ...
location /api/ {
add_header 'Access-Control-Allow-Origin' $cors_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
# другие заголовки...
if ($cors_origin) {
add_header 'Vary' 'Origin';
}
# ...
}
}
Настройка CORS в IIS
В IIS настройка CORS может быть выполнена через модуль URL Rewrite и веб-конфигурацию. В файле web.config:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="https://trusted-site.com" />
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, DELETE" />
<add name="Access-Control-Allow-Headers" value="Content-Type, Authorization, Accept" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
<rewrite>
<rules>
<rule name="Options Method" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{REQUEST_METHOD}" pattern="^OPTIONS$" />
</conditions>
<action type="CustomResponse" statusCode="200" statusReason="OK" statusDescription="OK" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Для динамического определения разрешенных доменов потребуется создание HTTP-модуля или использование скриптов на ASP.NET.
| Сервер | Файл конфигурации | Основной метод настройки | Поддержка динамических доменов |
|---|---|---|---|
| Apache | .htaccess / httpd.conf | modheaders + modrewrite | Через SetEnvIfNoCase и переменные |
| Nginx | nginx.conf / site configs | add_header + map directive | Через map и переменные $http_origin |
| IIS | web.config | customHeaders + URL Rewrite | Требуется HTTP-модуль |
| Caddy | Caddyfile | header директивы | Через Caddyfile с шаблонами |
Практические решения для сложных сценариев CORS
При работе с CORS в сложных системах часто возникают нетривиальные задачи, требующие особых подходов. Рассмотрим наиболее эффективные решения для таких ситуаций. 🧩
Динамические CORS-заголовки
Если ваше приложение должно обслуживать множество клиентских доменов, хранение списка разрешенных доменов в базе данных может быть более гибким решением:
// Express.js пример с динамической проверкой доменов
const cors = require('cors');
const db = require('./database');
app.use(async (req, res, next) => {
const origin = req.headers.origin;
// Проверяем домен в базе данных
const allowed = await db.allowedOrigins.findOne({ domain: origin });
if (allowed) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// Обработка preflight-запросов
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
Прокси-сервер для обхода CORS
Когда у вас нет контроля над API, но нужно взаимодействовать с ним, можно использовать серверный прокси:
// Простой прокси на Express
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
app.get('/api/proxy', async (req, res) => {
try {
// URL API, к которому нужен доступ
const targetUrl = req.query.url;
// Проверка безопасности URL
if (!targetUrl.startsWith('https://trusted-api.com')) {
return res.status(403).json({ error: 'Forbidden URL' });
}
// Выполнение запроса от имени сервера
const response = await axios.get(targetUrl);
// Возврат данных клиенту
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
Wildcards и регулярные выражения
Разрешение поддоменов или групп доменов:
- В Express/Node.js можно использовать регулярные выражения для проверки поддоменов:
const corsOptions = {
origin: function (origin, callback) {
// Разрешить любой поддомен example.com
const allowedDomainRegex = /^https:\/\/([a-z0-9]+\.)?example\.com$/;
if (!origin || allowedDomainRegex.test(origin)) {
callback(null, true);
} else {
callback(new Error('CORS not allowed'));
}
}
};
Обработка аутентификации с CORS
При работе с аутентифицированными запросами особенно важно правильно настроить CORS:
- Обязательно укажите конкретные домены в
Access-Control-Allow-Origin(не используйте wildcard * при работе с credentials) - Установите
Access-Control-Allow-Credentials: true - Включите все необходимые заголовки авторизации в
Access-Control-Allow-Headers - На стороне клиента установите
{ credentials: 'include' }при использовании fetch
// Серверная сторона (Express)
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
// Клиентская сторона (fetch)
fetch('https://api.example.com/user-data', {
method: 'GET',
credentials: 'include',
headers: {
'Authorization': 'Bearer ' + token
}
})
.then(response => response.json())
.then(data => console.log(data));
Тестирование CORS-конфигурации
Важно тщательно тестировать CORS-настройки перед выпуском в production:
- Используйте инструменты вроде Postman для тестирования API с разными Origin-заголовками
- Создайте специальную тестовую страницу, которая выполняет CORS-запросы с различных доменов
- Проверяйте как простые, так и preflight-запросы
- Тестируйте с разными браузерами, так как их реализация CORS может немного отличаться
Пример простой тестовой страницы:
<!DOCTYPE html>
<html>
<head>
<title>CORS Test</title>
<script>
function testCORS() {
fetch('https://api.example.com/test', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Custom-Header': 'test'
}
})
.then(response => response.json())
.then(data => {
document.getElementById('result').textContent = JSON.stringify(data);
})
.catch(error => {
document.getElementById('result').textContent = error.toString();
});
}
</script>
</head>
<body>
<button onclick="testCORS()">Test CORS</button>
<div id="result"></div>
</body>
</html>
Настройка CORS — это не разовое действие, а постоянный процесс совершенствования безопасности вашего приложения. Понимание механизмов работы CORS позволит вам создавать системы, которые одновременно открыты для необходимых взаимодействий и защищены от нежелательного доступа. Используйте полученные знания, чтобы превратить CORS из источника головной боли в надежный инструмент защиты ваших пользователей и данных. Будьте последовательны в применении принципа наименьших привилегий: разрешайте только необходимый доступ и только проверенным источникам.