CORS: как настроить кросс-доменные запросы для веб-разработки

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

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

  • Веб-разработчики, работающие с 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 на другой домен

Когда браузер выполняет межсайтовый запрос, происходит следующее:

  1. Браузер добавляет к запросу заголовок Origin, указывающий, откуда пришел запрос
  2. Сервер проверяет этот заголовок и решает, разрешить доступ или нет
  3. Если доступ разрешен, сервер добавляет к ответу заголовок Access-Control-Allow-Origin
  4. Браузер проверяет этот заголовок и либо разрешает доступ к ответу, либо блокирует его

Для сложных запросов (с нестандартными 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-ошибок следуйте этим шагам:

  1. Откройте консоль браузера и найдите точное сообщение об ошибке
  2. Используйте вкладку Network в инструментах разработчика, чтобы проверить заголовки запроса и ответа
  3. Проверьте, отправляется ли preflight-запрос (метод OPTIONS) и какой ответ приходит
  4. Убедитесь, что сервер правильно настроен для обработки 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:

  1. Обязательно укажите конкретные домены в Access-Control-Allow-Origin (не используйте wildcard * при работе с credentials)
  2. Установите Access-Control-Allow-Credentials: true
  3. Включите все необходимые заголовки авторизации в Access-Control-Allow-Headers
  4. На стороне клиента установите { 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 из источника головной боли в надежный инструмент защиты ваших пользователей и данных. Будьте последовательны в применении принципа наименьших привилегий: разрешайте только необходимый доступ и только проверенным источникам.

Загрузка...