Same-origin policy и CORS: принципы защиты и настройка доступа
Перейти

Same-origin policy и CORS: принципы защиты и настройка доступа

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

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

  • Веб-разработчики и программисты
  • Специалисты по безопасности приложений
  • Студенты и обучающиеся в области информационных технологий

Веб-приложения постоянно взаимодействуют с различными источниками данных, но не все эти взаимодействия безопасны. Когда ваш код пытается получить ресурсы с "чужого" домена, браузер немедленно блокирует запрос, вызывая недоумение и разочарование разработчиков. 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 можно разделить на два типа запросов:

  1. Простые запросы — не требуют предварительной проверки
  2. Предварительные запросы (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:

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

nginx
Скопировать код
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 или конфигурацию сайта:

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

Python
Скопировать код
# 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 может быть реализована несколькими способами:

Java
Скопировать код
// Метод 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 запроса против базы данных разрешенных доменов:

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

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

JS
Скопировать код
// Ошибка из-за отсутствия заголовка 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'.

Шаги для диагностики:

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

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Какое правило безопасности не позволяет веб-страницам загружать данные с других сайтов без разрешения?
1 / 5

Элина Баранова

разработчик Android

Свежие материалы

Загрузка...