Как решить проблему CORS: настройка кросс-доменных запросов
Для кого эта статья:
- Веб-разработчики и программисты, работающие с API и фронтенд-технологиями
- Студенты и начинающие специалисты, интересующиеся обучением веб-разработке
Опытные разработчики, желающие обновить свои знания о CORS и кросс-доменных запросах
Каждый веб-разработчик рано или поздно сталкивается с сообщением об ошибке "Access to XMLHttpRequest has been blocked by CORS policy". Эта ошибка способна превратить обычный рабочий день в многочасовую головную боль. Если вы когда-либо пытались получить данные с другого домена и ваш браузер категорически отказывался это делать — вы не одиноки. Давайте разберемся, что такое CORS, почему он существует и какие существуют способы настроить кросс-доменные запросы, чтобы ваше приложение работало как часы. 🚀
Если кросс-доменные запросы и CORS для вас всё ещё тёмный лес, пора получить структурированные знания с обучением веб-разработке от Skypro. Наши курсы не только раскрывают теорию, но и дают практические навыки работы с API, включая настройку CORS и безопасное взаимодействие между доменами. Забудьте о бесконечном гуглении ошибок — учитесь решать проблемы профессионально под руководством опытных менторов!
Что такое CORS и политика одного источника
CORS (Cross-Origin Resource Sharing) — механизм, который позволяет серверу указать, каким доменам разрешено получать ресурсы с этого сервера. По умолчанию веб-браузеры блокируют запросы с одного домена (источника) на другой из соображений безопасности — это называется Same-Origin Policy (политика одного источника).
Источник (origin) в контексте веб-безопасности определяется тремя составляющими:
- Протокол (http, https)
- Домен (example.com)
- Порт (80, 443 и т.д.)
Если хотя бы один из этих компонентов отличается — браузер считает запрос кросс-доменным и применяет ограничения.
Артём Васильев, руководитель отдела фронтенд-разработки
Два года назад наша команда разрабатывала приложение, которое должно было взаимодействовать с API партнёра. Всё работало отлично на локальном сервере, но когда мы выкатили продукт в продакшн, всё рухнуло из-за CORS-ошибок. Мы потратили три дня на диагностику, пока не осознали фундаментальную проблему: наш фронтенд находился на поддомене client.example.com, а API — на api.example.com. Хоть это и был один домен второго уровня, для браузера это были разные источники! Решение оказалось простым — нужно было настроить заголовок Access-Control-Allow-Origin на сервере API. Но этот опыт научил меня всегда начинать с проверки CORS при разработке распределённых систем.
Политика одного источника защищает пользователей от атак типа CSRF (Cross-Site Request Forgery) и XSS (Cross-Site Scripting), но одновременно создаёт трудности для легитимного взаимодействия между сервисами.
| URL | Тот же источник? | Причина |
|---|---|---|
| https://example.com/page2 | Да | Совпадает протокол, домен и порт |
| http://example.com | Нет | Отличается протокол (http vs https) |
| https://example.com:8080 | Нет | Отличается порт (443 по умолчанию vs 8080) |
| https://api.example.com | Нет | Отличается домен (поддомен) |
| https://othersite.com | Нет | Полностью другой домен |
Когда браузер блокирует кросс-доменный запрос, в консоли обычно появляется ошибка, похожая на эту:
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://app.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Теперь рассмотрим методы решения этой проблемы. 👨💻

Метод 1: Настройка CORS-заголовков на сервере
Самый правильный и надёжный способ обработки кросс-доменных запросов — настроить CORS-заголовки на сервере. Это позволяет серверу явно указать, каким доменам разрешено получать его ресурсы.
Основные CORS-заголовки:
- Access-Control-Allow-Origin: указывает, каким доменам разрешён доступ к ресурсу
- Access-Control-Allow-Methods: определяет разрешённые HTTP-методы (GET, POST, etc.)
- Access-Control-Allow-Headers: указывает, какие заголовки могут использоваться в запросе
- Access-Control-Allow-Credentials: разрешает отправку куки и аутентификационных данных
- Access-Control-Max-Age: определяет, как долго браузер может кешировать результаты предварительного запроса (preflight)
Вот пример настройки CORS на разных серверных платформах:
| Платформа | Код настройки CORS |
|---|---|
| Node.js (Express) |
|
| Python (Flask) |
|
| PHP |
|
| Apache (.htaccess) |
|
Важно понимать концепцию предварительных запросов (preflight requests). Когда браузер отправляет "нетривиальный" запрос (например, POST с JSON-данными), он сначала делает предварительный OPTIONS-запрос, чтобы узнать, разрешён ли основной запрос. Только после положительного ответа отправляется основной запрос.
При использовании звездочки Access-Control-Allow-Origin: * любой домен сможет получать ресурсы. Это удобно для публичного API, но небезопасно для приватных данных. Особенно если используется Access-Control-Allow-Credentials: true — в этом случае звездочка не работает, нужно явно указывать домен. 🔒
Метод 2: Использование JSONP для обхода ограничений
JSONP (JSON with Padding) — старый, но всё ещё работающий способ обхода Same-Origin Policy. Этот метод использует тот факт, что теги <script> не подчиняются ограничениям политики одного источника.
Принцип работы JSONP:
- Клиент создаёт тег
<script>с URL внешнего ресурса и добавляет параметр с именем функции обратного вызова - Сервер возвращает JavaScript-код, который вызывает указанную функцию с данными в качестве параметра
- Браузер выполняет полученный скрипт, что приводит к вызову функции обратного вызова с данными
Пример использования JSONP в jQuery:
$.ajax({<br>
url: 'https://api.example.com/data',<br>
dataType: 'jsonp',<br>
success: function(data) {<br>
console.log('Данные получены:', data);<br>
}<br>
});
А вот как выглядит реализация без библиотек:
function handleResponse(data) {<br>
console.log('Данные получены:', data);<br>
}<br><br>
const script = document.createElement('script');<br>
script.src = 'https://api.example.com/data?callback=handleResponse';<br>
document.head.appendChild(script);
Сервер должен обернуть ответ в вызов функции:
// Ответ сервера<br>
handleResponse({"name": "John", "age": 30});
Игорь Петров, архитектор веб-приложений
Пару лет назад мне пришлось поддерживать legacy-проект, который активно использовал JSONP для получения данных с разных доменов. Сайт работал нормально, пока не появилась необходимость отправлять конфиденциальные данные на сервер. JSONP поддерживает только GET-запросы, а данные передаются в URL — это серьезная проблема безопасности. Мы провели технический аудит и обнаружили, что пароли и личные данные пользователей передавались в открытом виде! К тому же, JSONP-запросы невозможно отменить, что создавало проблемы с производительностью. Нам пришлось переписать всю систему API-запросов, настроив правильные CORS-заголовки и перейдя на современные методы. Этот опыт научил меня тщательно оценивать недостатки технологий, особенно когда речь идет о безопасности пользовательских данных.
Преимущества и недостатки JSONP:
- ✅ Работает в старых браузерах
- ✅ Простая реализация
- ❌ Поддерживает только GET-запросы
- ❌ Нет стандартной обработки ошибок
- ❌ Уязвим к XSS-атакам если сервер не валидирует данные
- ❌ Нельзя отменить запрос
В современной разработке JSONP используется редко из-за своих ограничений и проблем с безопасностью. Лучше использовать правильно настроенный CORS. Однако знание этого метода может пригодиться при работе с устаревшими системами. 🕰️
Метод 3: Прокси-серверы как посредники при запросах
Когда нет возможности настроить CORS-заголовки на целевом сервере (например, при работе с чужим API без CORS-поддержки), на помощь приходят прокси-серверы. Суть метода: вместо прямого обращения к внешнему API, фронтенд обращается к своему бэкенду, который затем делает запрос к внешнему API и возвращает результат.
Поскольку серверные запросы не подчиняются ограничениям Same-Origin Policy, эта схема работает безупречно. 🔄
Варианты реализации прокси-сервера:
- Собственный бэкенд: создание эндпоинта, перенаправляющего запросы
- Серверные функции: использование serverless functions (AWS Lambda, Netlify Functions, Vercel API Routes)
- Прокси-middleware: настройка прокси в инструментах разработки (например, в webpack-dev-server)
- Готовые прокси-сервисы: использование CORS-proxy как сервиса
Пример прокси-эндпоинта на Node.js с Express:
const express = require('express');<br>
const axios = require('axios');<br>
const app = express();<br><br>
app.get('/api/proxy', async (req, res) => {<br>
try {<br>
const targetUrl = req.query.url;<br>
const response = await axios.get(targetUrl);<br>
res.json(response.data);<br>
} catch (error) {<br>
res.status(500).json({ error: error.message });<br>
}<br>
});<br><br>
app.listen(3000, () => {<br>
console.log('Прокси-сервер запущен на порту 3000');<br>
});
Использование прокси в клиентском коде:
// Вместо прямого запроса к https://external-api.com/data<br>
fetch('/api/proxy?url=https://external-api.com/data')<br>
.then(response => response.json())<br>
.then(data => console.log(data))<br>
.catch(error => console.error(error));
При настройке прокси важно учитывать следующие аспекты:
- Безопасность: валидировать входящие URL и ограничивать доступные домены
- Аутентификация: правильно передавать токены и другие данные авторизации
- Кеширование: настраивать кеширование для повышения производительности
- Ограничение частоты: защищать от DoS-атак и соблюдать лимиты внешнего API
- Обработка ошибок: корректно обрабатывать и возвращать ошибки клиенту
Для разработки можно настроить прокси в инструментах сборки. Например, в webpack-dev-server:
// webpack.config.js<br>
module.exports = {<br>
// ...<br>
devServer: {<br>
proxy: {<br>
'/api': {<br>
target: 'https://external-api.com',<br>
pathRewrite: { '^/api': '' },<br>
changeOrigin: true<br>
}<br>
}<br>
}<br>
};
Прокси-подход особенно полезен при разработке, когда нужно быстро интегрироваться с внешними API, не настраивая CORS. Однако для продакшн-окружения рекомендуется настраивать CORS правильно или использовать более надёжные прокси-решения. 💼
Метод 4: WebSockets и postMessage для обмена данными
WebSockets и postMessage API предоставляют альтернативные способы обмена данными между разными доменами, обходя ограничения Same-Origin Policy.
WebSockets устанавливают постоянное двунаправленное соединение между клиентом и сервером. В отличие от обычных HTTP-запросов, после установки соединения WebSockets не подчиняются ограничениям CORS.
Пример использования WebSockets:
// Клиентский код<br>
const socket = new WebSocket('wss://api.example.com/socket');<br><br>
socket.onopen = function() {<br>
console.log('Соединение установлено');<br>
socket.send(JSON.stringify({ type: 'getData', payload: { id: 123 } }));<br>
};<br><br>
socket.onmessage = function(event) {<br>
const data = JSON.parse(event.data);<br>
console.log('Получены данные:', data);<br>
};<br><br>
socket.onerror = function(error) {<br>
console.error('Ошибка WebSocket:', error);<br>
};<br><br>
socket.onclose = function() {<br>
console.log('Соединение закрыто');<br>
};
Метод postMessage позволяет безопасно обмениваться сообщениями между окнами, вкладками или iframe, даже если они находятся на разных доменах.
Пример использования postMessage между родительской страницей и iframe:
// На родительской странице (https://parent.com)<br>
const iframe = document.getElementById('childFrame');<br><br>
// Отправка сообщения в iframe<br>
iframe.onload = function() {<br>
iframe.contentWindow.postMessage({ type: 'getData', id: 123 }, 'https://child.com');<br>
};<br><br>
// Получение ответа от iframe<br>
window.addEventListener('message', function(event) {<br>
// Проверяем источник сообщения для безопасности<br>
if (event.origin !== 'https://child.com') return;<br><br>
console.log('Получены данные от iframe:', event.data);<br>
}, false);
// На странице iframe (https://child.com)<br>
window.addEventListener('message', function(event) {<br>
// Проверяем источник сообщения<br>
if (event.origin !== 'https://parent.com') return;<br><br>
const data = event.data;<br>
console.log('Получено сообщение от родителя:', data);<br><br>
// Отправляем ответ обратно родителю<br>
event.source.postMessage({ type: 'response', data: { name: 'Child Data' } }, event.origin);<br>
}, false);
Сравнение WebSockets и postMessage:
| Критерий | WebSockets | postMessage |
|---|---|---|
| Сценарий использования | Обмен данными между клиентом и сервером в реальном времени | Обмен данными между разными окнами/фреймами |
| Соединение | Постоянное двунаправленное | Эпизодическое, сообщение за раз |
| Необходимость сервера | Требуется WebSocket-сервер | Не требуется, работает только на клиенте |
| Безопасность | Поддерживает SSL/TLS (wss://) | Требует проверки origin для безопасности |
| Производительность | Низкая задержка, малые накладные расходы | Зависит от размера сообщений и производительности DOM |
Эти методы особенно полезны в следующих случаях:
- WebSockets: чаты, торговые платформы, игры, дашборды с обновлениями в реальном времени
- postMessage: интеграция с виджетами третьих сторон, общение между родительским и дочерними окнами, реализация SSO между разными доменами
При использовании этих методов всегда помните о безопасности: проверяйте источник сообщений (origin), валидируйте данные и используйте защищённые соединения. 🛡️
Метод 5: Локальные DNS записи и Host-файл
Для тестирования и разработки иногда эффективно использовать локальные DNS-настройки, чтобы разные части приложения работали на "одном домене". Этот метод обходит ограничения Same-Origin Policy, размещая разные сервисы на поддоменах одного корневого домена.
Например, если вам нужно взаимодействие между фронтендом и API, которые в продакшне будут на разных доменах, можно сделать так:
- Отредактировать файл hosts (Windows: C:\Windows\System32\drivers\etc\hosts, Unix/Linux: /etc/hosts)
- Добавить записи для локальных доменов:
127.0.0.1 app.local
127.0.0.1 api.app.local
- Настроить локальные серверы для прослушивания на этих доменах
Теперь фронтенд на app.local и API на api.app.local будут считаться поддоменами одного домена, и между ними можно настроить общие куки с атрибутом Domain=".app.local". При этом доступ по API работает без CORS-проблем, если правильно настроить общий домен для куки.
Этот метод полезен для локальной разработки, но не для продакшн-окружения, где всё равно потребуется настройка CORS. 🛠️
Технические решения для кросс-доменных запросов помогают преодолеть ограничения веб-безопасности. Выбор метода зависит от конкретного сценария: для современных приложений лучше настраивать CORS-заголовки, для взаимодействия с не-CORS API подойдут прокси, а для обмена данными в реальном времени — WebSockets. Постоянно развивайте свои навыки безопасной разработки и помните, что ограничения существуют не для усложнения жизни разработчиков, а для защиты пользователей от потенциальных угроз.