CORS-ошибки в браузере и Postman: причины и решения безопасности
Для кого эта статья:
- Разработчики веб-приложений, работающие с API и CORS.
- Студенты и обучающиеся, желающие освоить веб-разработку и безопасность.
Специалисты по DevOps и системные администраторы, занимающиеся настройкой серверов и безопасности.
Знакомая ситуация: вы разрабатываете фронтенд, который отправляет запрос к API, и внезапно — красное сообщение об ошибке CORS в консоли браузера. Но когда вы отправляете абсолютно идентичный запрос через Postman, всё работает идеально! 🤔 Почему браузер так трепетно охраняет ваши данные, в то время как Postman легко преодолевает эти барьеры? Это не просто технический нюанс, а фундаментальное различие в подходах к безопасности, которое может стать или головной болью, или мощным защитным механизмом — в зависимости от того, насколько вы понимаете принципы его работы.
Если вы устали от бесконечных боев с CORS-ошибками и хотите раз и навсегда освоить тонкости веб-безопасности, обратите внимание на обучение веб-разработке от Skypro. Здесь вы не только получите практические навыки настройки CORS и работы с API, но и глубоко изучите безопасность веб-приложений под руководством экспертов, которые ежедневно сталкиваются с подобными вызовами в реальных проектах. 💻
Что такое CORS и почему браузер блокирует запросы
Cross-Origin Resource Sharing (CORS) — это механизм безопасности, который позволяет серверу указывать, каким доменам разрешено обращаться к его ресурсам. По сути, это способ контролировать, кто может взаимодействовать с вашим API через браузер. 🔒
Представьте CORS как строгого охранника, который проверяет каждый входящий запрос: "А у вас есть разрешение доступа от владельца ресурса?" Если разрешения нет — запрос блокируется.
Александр, Senior Frontend Developer
Я провел почти два дня, пытаясь понять, почему мой React-проект не может получить данные с нового API. Консоль кричала о CORS-ошибках, хотя тот же запрос в Postman работал без проблем. Оказалось, что проблема была в заголовках ответа сервера. После добавления
Access-Control-Allow-Origin: *на бэкенде все заработало. Но этот опыт заставил меня глубже изучить, почему браузеры так строги к кросс-доменным запросам, и я понял — это не баг, а важнейшая защитная функция.
Истоки CORS уходят в политику одного источника (Same-Origin Policy), которая является одним из краеугольных камней веб-безопасности. Согласно этой политике, документ или скрипт, загруженный с одного источника, не может взаимодействовать с ресурсами с другого источника. Источник определяется комбинацией протокола (HTTP/HTTPS), хоста (домена) и порта.
Почему это важно? Представьте, что вы авторизованы в своем банке через браузер. Без Same-Origin Policy и CORS вредоносный сайт мог бы отправлять запросы к API вашего банка от вашего имени, используя ваши куки и токены авторизации, которые браузер автоматически прикрепляет к запросам.
| Запрос от | Запрос к | Считается cross-origin? |
|---|---|---|
| https://example.com | https://example.com/api/data | Нет (тот же источник) |
| https://example.com | https://api.example.com | Да (другой поддомен) |
| https://example.com | http://example.com | Да (другой протокол) |
| https://example.com | https://example.com:8080 | Да (другой порт) |
Когда браузер обнаруживает кросс-доменный запрос, он выполняет несколько действий:
- Для простых запросов (GET, HEAD, POST с определенными типами контента) — добавляет заголовок
Origin, указывающий источник запроса. - Для сложных запросов (PUT, DELETE, или POST с JSON) — сначала отправляет предварительный запрос OPTIONS, чтобы узнать, разрешен ли основной запрос.
- Проверяет ответ сервера на наличие разрешающих CORS-заголовков.
Если сервер не включает соответствующие CORS-заголовки в ответ, браузер блокирует запрос, и вы видите знаменитую ошибку в консоли: "Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy".

Механизмы безопасности в браузере vs Postman
Браузеры и Postman принципиально различаются в том, как они обрабатывают веб-запросы, особенно в контексте безопасности. Эти различия объясняют, почему запрос, блокируемый в Chrome или Firefox, может беспрепятственно проходить через Postman. 🛡️
| Аспект безопасности | Браузер | Postman |
|---|---|---|
| Same-Origin Policy | Строго соблюдает | Не применяется |
| CORS проверки | Обязательны | Отсутствуют |
| Автоматическая отправка cookie | Только для same-origin запросов по умолчанию | По выбору пользователя |
| Предварительные запросы (preflight) | Автоматически для сложных запросов | Только если явно настроено |
| Целевая аудитория | Конечные пользователи | Разработчики/тестировщики |
Браузер изначально разработан как среда выполнения для потенциально небезопасного кода. Когда вы открываете веб-страницу, браузер загружает и выполняет JavaScript с этого сайта. Без строгих мер безопасности вредоносный сайт мог бы:
- Совершать транзакции от вашего имени на других сайтах, где вы авторизованы
- Читать конфиденциальные данные из вашего онлайн-банкинга
- Отправлять фишинговые сообщения от вашего имени через почтовые сервисы
- Манипулировать настройками вашего роутера или других устройств в локальной сети
Postman, напротив, разработан как инструмент специально для тестирования и разработки API. В контексте Postman предполагается, что вы как разработчик понимаете, что делаете, и сами контролируете безопасность своих запросов.
Екатерина, DevOps-инженер
В нашем проекте возникла интересная ситуация при интеграции с внешним API. Фронтенд-разработчики жаловались на постоянные CORS-ошибки, хотя документация API утверждала, что поддержка CORS включена. Я решила глубже разобраться с помощью Postman и сетевых инструментов браузера.
Оказалось, что API действительно отправлял CORS-заголовки, но только для определенных HTTP-методов. Наш фронтенд использовал PUT-запросы, которые требовали preflight-запросов OPTIONS, но сервер не был настроен отвечать на них корректно.
Я создала прокси-сервер на Node.js, который добавлял необходимые заголовки ко всем ответам от внешнего API. Это временное решение позволило команде продолжить разработку, пока мы согласовывали правильную настройку CORS с владельцами API. Этот случай отлично продемонстрировал, насколько важно понимать разницу между тем, как работают запросы в браузере и в инструментах вроде Postman.
Важно понимать, что браузер не "капризничает" и не создаёт проблемы для разработчиков — он защищает пользователей. CORS и другие механизмы безопасности были разработаны после обнаружения серьёзных уязвимостей и атак на веб-приложения.
Кроме CORS, браузеры реализуют и другие механизмы защиты:
- Content Security Policy (CSP) — позволяет указать, из каких источников могут загружаться ресурсы
- HttpOnly Cookies — запрещают JavaScript доступ к определённым cookie
- Secure Cookies — отправляются только по HTTPS-соединениям
- X-Frame-Options — контролирует возможность встраивания сайта в iframe
Postman обходит все эти ограничения, потому что он выполняет запросы непосредственно через сеть, без браузерного контекста и связанных с ним политик безопасности.
Как Postman обходит ограничения CORS
Postman не просто "обходит" ограничения CORS — он в принципе существует вне этой концепции. 🔍 Понимание точного механизма этого процесса помогает разработчикам лучше диагностировать проблемы кросс-доменных запросов.
Когда вы отправляете запрос через Postman, происходит следующее:
- Запрос формируется напрямую в приложении Postman.
- Postman использует HTTP-клиент, встроенный в приложение (не браузерный API).
- Запрос отправляется непосредственно на целевой сервер без браузерной обёртки.
- Сервер отвечает, и Postman отображает полученный ответ.
В этом процессе отсутствует критическая стадия — проверка CORS-заголовков браузером. Postman действует как самостоятельный HTTP-клиент, а не как JavaScript в контексте веб-страницы.
Важно понимать, что Postman на самом деле ближе к таким инструментам, как curl или wget, чем к браузеру. Он отправляет "чистые" HTTP-запросы без дополнительной логики безопасности, присущей браузерам.
| Этап запроса | Браузер | Postman |
|---|---|---|
| Проверка источника запроса | Проверяет соответствие same-origin policy | Нет проверки |
| Предварительный запрос OPTIONS | Отправляет для сложных запросов | Не отправляет автоматически |
| Добавление Origin заголовка | Автоматически добавляет | Только если добавлен вручную |
| Проверка CORS заголовков в ответе | Проверяет и блокирует при несоответствии | Игнорирует |
| Выполнение JavaScript из ответа | Может выполнять (если разрешено) | Никогда не выполняет |
Для разработчиков это создаёт интересную ситуацию: Postman отлично подходит для тестирования API, но успешные тесты в Postman не гарантируют, что запросы будут работать в браузере. 🧪
Тем не менее, Postman предоставляет несколько функций, которые помогают разработчикам лучше понять CORS и отладить связанные с ним проблемы:
- Code Generation — позволяет генерировать код запроса для различных языков и фреймворков
- Заголовки запроса — можно добавить заголовок
Originвручную, чтобы симулировать браузерное поведение - Предварительные запросы — можно вручную создать OPTIONS запрос для проверки ответа сервера
- Interceptor — расширение, позволяющее Postman перехватывать и воспроизводить запросы из браузера
Важно помнить, что хотя Postman позволяет обойти ограничения CORS при тестировании, ваше веб-приложение всё равно будет подчиняться этим правилам в браузере. Поэтому правильная настройка CORS на сервере остаётся обязательным требованием для работы кросс-доменных запросов.
Распространённые CORS ошибки в браузере и их решение
Столкнувшись с CORS-ошибками, разработчики часто погружаются в пучину отладки, не понимая, какой именно аспект политики безопасности вызывает проблему. Давайте разберем наиболее распространённые ошибки CORS и способы их решения. 🚨
Ошибки CORS обычно содержат ключевую информацию, указывающую на причину блокировки запроса. Внимательное чтение сообщения в консоли — первый шаг к решению проблемы.
- Отсутствие заголовка Access-Control-Allow-Origin
Ошибка: "Access to XMLHttpRequest at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
Решение: На сервере необходимо добавить заголовок
Access-Control-Allow-Originс указанием разрешенного источника или "*" для разрешения всех источников (не рекомендуется для производственной среды). Код на Node.js (Express):
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://app.example.com');
next();
});
- Несоответствие метода в списке разрешенных
Ошибка: "Method PUT is not allowed by Access-Control-Allow-Methods in preflight response."
Решение: Добавьте используемый метод в заголовок
Access-Control-Allow-Methods.
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
- Проблема с пользовательскими заголовками
Ошибка: "Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response."
Решение: Включите все пользовательские заголовки в
Access-Control-Allow-Headers.
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Custom-Header');
- Проблема с отправкой учётных данных
Ошибка: "The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'."
Решение: Убедитесь, что сервер отправляет
Access-Control-Allow-Credentials: true, если вы используетеwithCredentials: trueв запросах.
// На сервере
res.header('Access-Control-Allow-Credentials', 'true');
// На клиенте
fetch(url, { credentials: 'include' });
Важно: при использовании credentials нельзя устанавливать Access-Control-Allow-Origin: *, нужно указать конкретный домен.
- Неправильная настройка preflight-кэширования
Симптом: Большое количество OPTIONS-запросов замедляет работу приложения.
Решение: Установите заголовок
Access-Control-Max-Ageдля кэширования результатов preflight-запроса.
// Кэшировать результат preflight-запроса на 1 час (3600 секунд)
res.header('Access-Control-Max-Age', '3600');
Для эффективной отладки CORS-проблем используйте следующие инструменты:
- Инструменты разработчика в браузере — вкладка Network для анализа заголовков запросов и ответов
- CORS-расширения для браузера — позволяют временно отключить CORS-проверки в браузере для отладки (не для производственной среды!)
- Прокси-серверы — например, cors-anywhere, которые могут добавлять CORS-заголовки к запросам
Если вы работаете с локальным сервером разработки, многие фреймворки предоставляют встроенные инструменты для настройки CORS:
- React с Create React App: настройка прокси в package.json
- Vue с Vue CLI: опция devServer.proxy в vue.config.js
- Angular: настройка прокси в proxy.conf.json
Помните: временные решения (CORS-расширения, прокси) хороши для отладки, но в производственной среде CORS должен быть корректно настроен на сервере. 🔧
Настройка заголовков CORS для безопасного API
Правильная настройка CORS — это баланс между функциональностью и безопасностью. Слишком строгие ограничения могут помешать работе приложения, слишком свободные — подвергнуть ваш API риску. 🛡️ Рассмотрим детально, как настроить CORS максимально безопасно на различных серверных платформах.
Общие принципы безопасной настройки CORS:
- Избегайте
Access-Control-Allow-Origin: *в производственной среде - Указывайте только необходимые домены в заголовке Origin
- Ограничьте список методов только теми, которые действительно используются
- Будьте осторожны с
Access-Control-Allow-Credentials: true— это требует точного указания Origin - Используйте
Access-Control-Expose-Headersтолько для необходимых заголовков
Примеры настройки CORS на различных серверных платформах:
1. Node.js с Express
const express = require('express');
const cors = require('cors');
const app = express();
// Базовая настройка (не для продакшн!)
app.use(cors());
// Продвинутая настройка для продакшн
const corsOptions = {
origin: ['https://trusted-app.com', 'https://admin.trusted-app.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['Content-Range', 'X-Total-Count'],
credentials: true,
maxAge: 3600
};
app.use(cors(corsOptions));
2. Python с Flask
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Продвинутая настройка
cors = CORS(
app,
resources={r"/api/*": {
"origins": ["https://trusted-app.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"expose_headers": ["Content-Range"],
"supports_credentials": True,
"max_age": 3600
}}
)
3. Java Spring Boot
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://trusted-app.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "Authorization")
.exposedHeaders("Content-Range", "X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
4. PHP
<?php
header("Access-Control-Allow-Origin: https://trusted-app.com");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Expose-Headers: Content-Range, X-Total-Count");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 3600");
// Обработка preflight запросов OPTIONS
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
// Основная логика приложения...
?>
Для динамического определения разрешенного источника на основе списка доменов можно использовать следующий подход (пример для Node.js):
const allowedOrigins = [
'https://app.example.com',
'https://admin.example.com',
// Для разработки
'http://localhost:3000'
];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
}
// Остальные заголовки
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
Помимо настройки заголовков, важно правильно обрабатывать preflight-запросы OPTIONS:
app.options('*', (req, res) => {
// Заголовки уже установлены middleware выше
res.status(204).end();
});
Для максимальной безопасности рассмотрите также дополнительные меры:
- Используйте rate limiting для защиты от DDoS-атак
- Реализуйте токены CSRF для защиты от Cross-Site Request Forgery
- Настройте Content Security Policy (CSP) для дополнительного уровня защиты
- Регулярно проверяйте журналы доступа к API для выявления подозрительной активности
Помните, что настройки CORS должны быть адаптированы под конкретные потребности вашего API и приложения. Регулярно пересматривайте эти настройки, особенно при изменении доменов, с которых осуществляется доступ к API. 📝
Понимание принципов CORS — это не просто навык отладки, а фундаментальная компетенция веб-разработчика. Правильная настройка CORS отражает ваше понимание баланса между удобством использования и безопасностью. Когда в следующий раз вы столкнетесь с CORS-ошибкой, вспомните, что браузер не "мешает" вам работать — он защищает ваших пользователей. А ваша задача как профессионала — научиться работать в рамках этой защиты, создавая безопасные и функциональные веб-приложения. Используйте описанные подходы к настройке серверной части, и ваши кросс-доменные запросы будут работать так же надежно, как в Postman, но с полным соблюдением принципов безопасности.