Same-origin policy и CORS: принципы защиты и настройка доступа
#Веб-разработка #Безопасность (XSS/CSRF) #Веб-безопасностьДля кого эта статья:
- Веб-разработчики и программисты
- Специалисты по безопасности приложений
- Студенты и обучающиеся в области информационных технологий
Веб-приложения постоянно взаимодействуют с различными источниками данных, но не все эти взаимодействия безопасны. Когда ваш код пытается получить ресурсы с "чужого" домена, браузер немедленно блокирует запрос, вызывая недоумение и разочарование разработчиков. Same-Origin Policy и CORS — не просто аббревиатуры в ошибках консоли, а фундаментальные механизмы безопасности, защищающие миллионы пользователей ежедневно. Понимание этих механизмов — не опция, а необходимость для каждого веб-разработчика, желающего создать надежные и безопасные приложения. 🔐
Основы Same-Origin Policy: механизмы и ограничения
Same-Origin Policy (SOP) — один из краеугольных камней веб-безопасности, предотвращающий несанкционированный доступ к данным между различными источниками. Браузеры используют SOP как первую линию защиты от атак, включая кросс-сайтовый скриптинг (XSS) и кражу конфиденциальных данных.
Источник (origin) определяется тремя компонентами: протоколом, хостом и портом. Запросы считаются "same-origin", только когда все три компонента совпадают:
| URL | Совпадает с https://example.com? | Причина |
|---|---|---|
| https://example.com/page2 | Да | Только путь отличается |
| http://example.com | Нет | Протокол отличается (http vs https) |
| https://api.example.com | Нет | Поддомен отличается |
| https://example.com:8080 | Нет | Порт отличается |
SOP накладывает следующие ограничения на доступ между разными источниками:
- JavaScript: Скрипт с одного домена не может читать или изменять DOM-дерево страницы с другого домена
- AJAX-запросы: XMLHttpRequest и Fetch API по умолчанию ограничены источником страницы
- Cookies: JavaScript не может получить доступ к cookies других доменов
- LocalStorage/SessionStorage: Хранилища строго привязаны к своему источнику
Однако SOP не всегда мешает взаимодействию. Существуют разрешенные кросс-доменные операции:
- Встраивание изображений через теги
<img> - Загрузка CSS с использованием
<link> - Подключение скриптов через
<script> - Встраивание
<iframe>(хотя доступ к содержимому iframe с другого домена ограничен)
Антон Вершинин, Security Lead разработчик
Несколько лет назад мы столкнулись с интересным случаем, когда финансовое приложение одного из клиентов внезапно стало уязвимым. Проблема заключалась в том, что разработчики использовали динамическую загрузку скриптов с внешнего CDN без должной проверки. Хотя SOP предотвратил прямой доступ к конфиденциальным данным, злоумышленники смогли встроить вредоносный код через разрешенный
<script>тег.Мы реализовали строгую проверку подлинности ресурсов с помощью Subresource Integrity (SRI), добавив атрибуты integrity и crossorigin к тегам script. Кроме того, настроили Content Security Policy, позволяющую загружать скрипты только с доверенных источников. Это наглядно демонстрирует, что даже с работающим SOP необходимы дополнительные уровни защиты.
Несмотря на свою эффективность, SOP создает серьезные барьеры для современных веб-приложений, которые часто должны взаимодействовать с API на других доменах. Именно здесь на сцену выходит CORS — механизм, позволяющий контролируемо ослабить ограничения SOP. 🔄

CORS: преодоление барьеров Same-Origin Policy
Cross-Origin Resource Sharing (CORS) — стандартный механизм, который расширяет SOP и позволяет серверу явно указать, каким источникам разрешен доступ к его ресурсам. CORS работает через специальные HTTP-заголовки, которые сервер добавляет к своим ответам.
Процесс CORS можно разделить на два типа запросов:
- Простые запросы — не требуют предварительной проверки
- Предварительные запросы (preflight) — браузер сначала отправляет OPTIONS-запрос для проверки разрешений
Запрос считается простым, если соответствует всем следующим критериям:
- Используется один из методов: GET, HEAD или POST
- Установлены только "безопасные" заголовки (Accept, Accept-Language, Content-Language, Content-Type)
- Если установлен Content-Type, то только один из: application/x-www-form-urlencoded, multipart/form-data или text/plain
- Не используются события XMLHttpRequest.upload
- Запрос не использует ReadableStream объект
Для простых запросов браузер автоматически добавляет заголовок Origin, указывающий источник запроса. Сервер, в свою очередь, должен ответить с заголовком Access-Control-Allow-Origin, содержащим либо запрашивающий источник, либо '*' (звездочку), разрешающую доступ всем доменам.
// Запрос от клиента
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.mywebsite.com
// Ответ сервера
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.mywebsite.com
Content-Type: application/json
Для более сложных запросов браузер автоматически выполняет предварительный запрос (preflight) с методом OPTIONS. Этот механизм проверяет, разрешен ли основной запрос, прежде чем отправить его:
// Предварительный запрос
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.mywebsite.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header
// Ответ сервера на preflight
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.mywebsite.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400
Основные заголовки CORS включают:
| Заголовок | Описание | Пример |
|---|---|---|
| Access-Control-Allow-Origin | Указывает, каким доменам разрешен доступ к ресурсу | https://example.com или * |
| Access-Control-Allow-Methods | Список разрешенных HTTP-методов | GET, POST, PUT, DELETE |
| Access-Control-Allow-Headers | Список разрешенных заголовков | Content-Type, Authorization |
| Access-Control-Allow-Credentials | Разрешает передачу учетных данных (cookies) | true |
| Access-Control-Max-Age | Время (в секундах) кеширования preflight-ответа | 86400 (24 часа) |
Особое внимание следует уделить работе с учетными данными. Если запрос включает cookies (withCredentials: true в JavaScript), сервер должен явно указать Access-Control-Allow-Credentials: true и не может использовать звездочку в Access-Control-Allow-Origin. Вместо этого нужно точно указать домен, которому предоставляется доступ. 🍪
Настройка CORS-заголовков на различных серверах
Правильная настройка CORS — важнейший шаг в обеспечении безопасного межсайтового взаимодействия. Рассмотрим, как настроить CORS на наиболее распространенных серверах и фреймворках.
1. Node.js (Express)
В Express самый простой способ — использовать middleware cors:
const express = require('express');
const cors = require('cors');
const app = express();
// Базовая настройка для всех маршрутов
app.use(cors({
origin: 'https://yourapp.com', // или массив доменов ['https://app1.com', 'https://app2.com']
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
// Или для отдельного маршрута
app.get('/api/restricted', cors({
origin: 'https://trusted-client.com',
credentials: true
}), (req, res) => {
res.json({ data: 'Доступ разрешен только trusted-client.com' });
});
app.listen(3000);
2. Nginx
Для веб-сервера Nginx CORS настраивается через добавление заголовков в блок location:
server {
listen 80;
server_name api.example.com;
location /api/ {
# Основные CORS-заголовки
add_header 'Access-Control-Allow-Origin' 'https://yourapp.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
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-Allow-Origin' 'https://yourapp.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
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';
add_header 'Access-Control-Max-Age' '86400';
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' '0';
return 204;
}
# Проксирование запросов к бэкенду
proxy_pass http://backend_server;
}
}
3. Apache
Для Apache настройка производится через .htaccess или конфигурацию сайта:
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://yourapp.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization"
Header set Access-Control-Allow-Credentials "true"
# Обработка preflight-запросов
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>
4. Python (Django)
Для Django удобно использовать django-cors-headers:
# settings.py
INSTALLED_APPS = [
# ...
'corsheaders',
# ...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
# Должен быть перед CommonMiddleware
'django.middleware.common.CommonMiddleware',
# ...
]
# Разрешить конкретные домены
CORS_ALLOWED_ORIGINS = [
"https://yourapp.com",
"https://sub.yourapp.com",
]
# Или разрешить все домены (не рекомендуется для продакшена)
# CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
]
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'origin',
'user-agent',
]
CORS_ALLOW_CREDENTIALS = True
CORS_PREFLIGHT_MAX_AGE = 86400 # 24 часа
5. Java (Spring Boot)
Для Spring Boot настройка CORS может быть реализована несколькими способами:
// Метод 1: Глобальная конфигурация через WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://yourapp.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
// Метод 2: Аннотация на уровне контроллера/метода
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "https://yourapp.com", maxAge = 3600)
public class UserController {
// ...
}
Настраивая CORS, следуйте принципу минимальных привилегий: разрешайте только те домены, методы и заголовки, которые действительно необходимы. Никогда не используйте wildcard (*) для Access-Control-Allow-Origin в продакшен-окружении при включенном флаге credentials. 🛡️
Расширенные сценарии использования CORS и безопасность
Стандартные настройки CORS покрывают большинство сценариев, но существуют продвинутые случаи, требующие особого подхода.
Михаил Зотов, DevOps-инженер
Однажды мы реализовывали микросервисную архитектуру для крупного e-commerce проекта. Фронтенд и различные API располагались на разных доменах. Нам требовалось обеспечить безопасное взаимодействие между всеми компонентами, включая передачу чувствительных данных пользователей.
Ключевой проблемой стала настройка динамических CORS-заголовков. Мы не могли просто прописать статический список доменов, так как для каждого клиента создавались поддомены в формате client-name.ourplatform.com. Решение нашлось в виде динамической генерации CORS-заголовков на основе базы данных клиентов.
Мы настроили Nginx в качестве обратного прокси, который при получении запроса проверял Origin против белого списка через API аутентификации и динамически устанавливал нужные CORS-заголовки. Для защиты от утечки данных мы также внедрили мониторинг нетипичных шаблонов запросов, что помогло выявить несколько попыток несанкционированного доступа.
Рассмотрим несколько специфических сценариев использования CORS:
1. Динамический список доменов
Когда требуется разрешить CORS для динамически меняющегося списка доменов (например, в мультитенантной системе), статические настройки не подходят. Решение: проверять заголовок Origin запроса против базы данных разрешенных доменов:
app.use((req, res, next) => {
const origin = req.headers.origin;
// Проверка origin в базе данных или другом источнике
isAllowedOrigin(origin).then(allowed => {
if (allowed) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
}
// Обработка preflight-запросов
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
});
2. Передача аутентификационных данных
Когда API требует аутентификацию, важно правильно настроить CORS для работы с credentials:
- На сервере: установить Access-Control-Allow-Credentials: true
- На клиенте: указать withCredentials: true в запросах
- Access-Control-Allow-Origin должен содержать конкретный домен, не *
// Клиентский код с fetch
fetch('https://api.example.com/data', {
method: 'POST',
credentials: 'include', // Важно для передачи cookies
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data));
3. Многоуровневая архитектура с микрофронтендами
В сложных системах, где микрофронтенды на разных доменах взаимодействуют с множеством микросервисов, CORS должен быть настроен на каждом уровне. Рекомендуется использовать API-шлюз, который централизованно управляет CORS-политиками:
// API Gateway с Express
const gateway = express();
gateway.use('/auth-service/*', cors({
origin: ['https://account.example.com', 'https://admin.example.com'],
methods: ['GET', 'POST'],
credentials: true
}), proxy('http://auth-service'));
gateway.use('/user-service/*', cors({
origin: ['https://account.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}), proxy('http://user-service'));
gateway.use('/admin-service/*', cors({
origin: 'https://admin.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}), proxy('http://admin-service'));
4. Безопасность при использовании CORS
При реализации CORS следует учитывать следующие аспекты безопасности:
- Тщательно выбирайте разрешенные домены: избегайте wildcard (*), особенно при использовании credentials
- Ограничивайте HTTP-методы: разрешайте только необходимые (GET, POST и т.д.)
- Минимизируйте разрешенные заголовки: включайте только те, которые требуются для функционирования
- Внедрите дополнительные механизмы защиты: CSRF-токены для защиты от атак с использованием cookies
- Используйте Content-Security-Policy (CSP): дополнительный уровень защиты от XSS-атак
- Регулярный аудит CORS-политик: проверяйте актуальность и безопасность настроек
Помните, что CORS — это механизм безопасности для браузера, но он не защищает от атак на стороне сервера. Злоумышленники могут напрямую отправлять запросы к вашему API, минуя браузер и, следовательно, CORS-ограничения. Поэтому всегда реализуйте полноценную аутентификацию и авторизацию на серверной стороне. 🔒
Отладка и решение распространенных проблем с CORS
CORS-ошибки — одни из самых частых и при этом часто неправильно интерпретируемых проблем при разработке веб-приложений. Рассмотрим основные проблемы и их решения.
1. Диагностика CORS-проблем
Типичные CORS-ошибки в консоли браузера:
// Ошибка из-за отсутствия заголовка Access-Control-Allow-Origin
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.
// Ошибка при несовпадении origin с разрешенными
Access to fetch at 'https://api.example.com/data' from origin 'https://app.com' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://different-app.com' that is not equal to the supplied origin.
// Ошибка при отсутствии заголовка Access-Control-Allow-Credentials
Access to fetch at 'https://api.example.com/data' from origin 'https://app.com' has been blocked by CORS policy: 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'.
Шаги для диагностики:
- Используйте инструменты разработчика в браузере (Network вкладка)
- Проверьте preflight-запросы (OPTIONS) и их ответы
- Убедитесь, что сервер возвращает правильные CORS-заголовки
- Проверьте, соответствует ли клиентский Origin тому, что указан в Access-Control-Allow-Origin
2. Распространенные проблемы и их решения
| Проблема | Возможные причины | Решение |
|---|---|---|
| Отсутствуют CORS-заголовки | CORS не настроен на сервере | Настройте соответствующие заголовки на сервере |
| Wildcards (*) не работают с credentials | Используется Access-Control-Allow-Origin: * вместе с credentials | Замените * на конкретный origin |
| Ошибка при использовании нестандартных заголовков | Заголовки не включены в Access-Control-Allow-Headers | Добавьте заголовки в Access-Control-Allow-Headers |
| Проблемы с preflight-запросами | Неправильная обработка OPTIONS запросов | Настройте корректную обработку OPTIONS на сервере |
| Cookies не передаются | Не установлен credentials: 'include' или withCredentials: true | Добавьте соответствующие параметры на клиенте и настройте Access-Control-Allow-Credentials на сервере |
3. Временные решения для разработки
Иногда требуется быстро обойти CORS для отладки:
- Локальный прокси: настройте локальный сервер-прокси, который будет добавлять необходимые CORS-заголовки
- Браузерные расширения: временно отключите CORS в браузере с помощью расширений (только для разработки!)
- DevServer с проксированием: используйте возможности Webpack, Vite или других инструментов для проксирования API-запросов
// Пример настройки прокси в webpack.config.js
module.exports = {
// ...
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
4. Инструменты для отладки CORS
Полезные инструменты для проверки и отладки CORS-проблем:
- curl: проверка заголовков запросов и ответов из командной строки
- Postman: тестирование API, включая preflight-запросы
- CORS Validator: онлайн-инструменты для проверки CORS-настроек
- Инструменты разработчика в браузере: Chrome DevTools, Firefox Developer Tools
5. Надежные практики CORS
- Проактивное тестирование: проверяйте CORS-настройки в CI/CD пайплайне
- Ведение журналов: логируйте и анализируйте отклоненные CORS-запросы
- Документация: четко документируйте CORS-политики для разработчиков, интегрирующих ваше API
- Мониторинг: настройте оповещения о необычных шаблонах CORS-запросов, которые могут указывать на попытки атаки
Важно понимать, что некоторые ошибки CORS могут появляться только в продакшен-окружении из-за различий в конфигурации серверов, SSL-сертификатов или прокси. Всегда тестируйте CORS-настройки в условиях, максимально приближенных к продакшену. 🧪
Мы рассмотрели фундаментальные принципы Same-Origin Policy и CORS — от базовых понятий до расширенных сценариев применения и отладки. Эти механизмы не просто технические ограничения, а краеугольные камни веб-безопасности, защищающие пользовательские данные и предотвращающие вредоносные атаки. Правильно настроенный CORS обеспечивает баланс между безопасностью и функциональностью, позволяя создавать современные веб-приложения с распределенной архитектурой. Помните, что конфигурация должна следовать принципу минимальных привилегий: разрешайте только необходимое и будьте бдительны, особенно при работе с конфиденциальными данными. Грамотная реализация этих принципов — признак профессионализма веб-разработчика и важный шаг к созданию безопасных приложений.
Элина Баранова
разработчик Android