Как решить проблему CORS: настройка кросс-доменных запросов

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

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

  • Веб-разработчики и программисты, работающие с 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)
javascript<br>app.use((req,
Скопировать код

|

| Python (Flask) |

python<br>from
Скопировать код

|

| PHP |

php<br>header("Access-Control-Allow-Origin:
Скопировать код

|

| Apache (.htaccess) |

apache<br>&lt;IfModule
Скопировать код

|

Важно понимать концепцию предварительных запросов (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:

  1. Клиент создаёт тег <script> с URL внешнего ресурса и добавляет параметр с именем функции обратного вызова
  2. Сервер возвращает JavaScript-код, который вызывает указанную функцию с данными в качестве параметра
  3. Браузер выполняет полученный скрипт, что приводит к вызову функции обратного вызова с данными

Пример использования JSONP в jQuery:

JS
Скопировать код
$.ajax({<br>
url: 'https://api.example.com/data',<br>
dataType: 'jsonp',<br>
success: function(data) {<br>
console.log('Данные получены:', data);<br>
}<br>
});

А вот как выглядит реализация без библиотек:

JS
Скопировать код
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);

Сервер должен обернуть ответ в вызов функции:

JS
Скопировать код
// Ответ сервера<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, эта схема работает безупречно. 🔄

Варианты реализации прокси-сервера:

  1. Собственный бэкенд: создание эндпоинта, перенаправляющего запросы
  2. Серверные функции: использование serverless functions (AWS Lambda, Netlify Functions, Vercel API Routes)
  3. Прокси-middleware: настройка прокси в инструментах разработки (например, в webpack-dev-server)
  4. Готовые прокси-сервисы: использование CORS-proxy как сервиса

Пример прокси-эндпоинта на Node.js с Express:

JS
Скопировать код
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>
});

Использование прокси в клиентском коде:

JS
Скопировать код
// Вместо прямого запроса к 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:

JS
Скопировать код
// 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:

JS
Скопировать код
// Клиентский код<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:

JS
Скопировать код
// На родительской странице (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);

JS
Скопировать код
// На странице 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, которые в продакшне будут на разных доменах, можно сделать так:

  1. Отредактировать файл hosts (Windows: C:\Windows\System32\drivers\etc\hosts, Unix/Linux: /etc/hosts)
  2. Добавить записи для локальных доменов:
127.0.0.1 app.local
127.0.0.1 api.app.local

  1. Настроить локальные серверы для прослушивания на этих доменах

Теперь фронтенд на app.local и API на api.app.local будут считаться поддоменами одного домена, и между ними можно настроить общие куки с атрибутом Domain=".app.local". При этом доступ по API работает без CORS-проблем, если правильно настроить общий домен для куки.

Этот метод полезен для локальной разработки, но не для продакшн-окружения, где всё равно потребуется настройка CORS. 🛠️

Технические решения для кросс-доменных запросов помогают преодолеть ограничения веб-безопасности. Выбор метода зависит от конкретного сценария: для современных приложений лучше настраивать CORS-заголовки, для взаимодействия с не-CORS API подойдут прокси, а для обмена данными в реальном времени — WebSockets. Постоянно развивайте свои навыки безопасной разработки и помните, что ограничения существуют не для усложнения жизни разработчиков, а для защиты пользователей от потенциальных угроз.

Загрузка...