Как работает заголовок ‘Access-Control-Allow-Origin’ в механизме кросс-ориджинального общего доступа к ресурсам (CORS)?
Я пытаюсь понять, как правильно реализовать заголовки CORS для того, чтобы код JavaScript с одного домена мог получать доступ к ресурсам на другом домене. Моё текущее понимание таково:
- Клиент загружает код JavaScript MyCode.js с http://siteA (оригинальный источник).
- Заголовок ответа для MyCode.js содержит Access-Control-Allow-Origin: http://siteB, что, как я думал, означало, что MyCode.js разрешено делать кросс-ориджинальные запросы к сайту B.
- Клиент запускает функциональность в MyCode.js, которая делает запросы к http://siteB.
Однако этот подход работает не так, как ожидалось. Я читал о кросс-ориджинальном общем доступе к ресурсам в Википедии и в рекомендации W3C, но я все еще не до конца понимаю, как правильно использовать этот заголовок.
Я имею контроль над обоими сайтами A и B. Как мне разрешить коду JavaScript, загруженному с сайта A, получать доступ к ресурсам на сайте B с использованием заголовка Access-Control-Allow-Origin?
Примечание: Я предпочитаю не использовать JSONP в качестве решения.
Заголовок Access-Control-Allow-Origin работает, указывая, какие источники (origins) имеют право доступа к ресурсу с другого домена. Когда браузер делает междоменный запрос, сервер отвечает этим заголовком, указывая, какие домены могут получить ответ. Этот заголовок должен быть установлен сервером, размещающим ресурс (siteB), а не сервером, предоставляющим код JavaScript (siteA). Это означает, что siteB должен включать Access-Control-Allow-Origin: http://siteA в свои ответы, чтобы JavaScript siteA мог получить доступ к его ресурсам.
Содержание
- Понимание заголовка Access-Control-Allow-Origin
- Как на самом деле работает CORS
- Предварительные запросы и метод OPTIONS
- Правильные шаги по реализации
- Распространенные ошибки и решения
- Вопросы безопасности
Понимание заголовка Access-Control-Allow-Origin
Заголовок Access-Control-Allow-Origin - это HTTP-заголовок ответа, который сообщает браузерам, может ли ресурс быть предоставлен коду, запрашивающему его с другого источника (origin). Когда вы контролируете и siteA, и siteB, вам необходимо настроить siteB на включение этого заголовка в его ответы.
Заголовок может иметь три возможных значения:
Access-Control-Allow-Origin: *- Разрешает доступ к ресурсу из любого источникаAccess-Control-Allow-Origin: http://siteA- Разрешает запросы только из конкретного источникаAccess-Control-Allow-Origin: null- Используется для непрозрачных ответов
Критическое ограничение безопасности заключается в том, что при работе с учетными данными сервер не может использовать подстановочный знак (*) и должен указать явный источник источник.
Ключевой момент: Заголовок Access-Control-Allow-Origin устанавливается сервером, который размещает запрашиваемый ресурс (siteB), а не сервером, предоставляющим код JavaScript (siteA). Именно почему ваш текущий подход не работает.
Как на самом деле работает CORS
Cross-Origin Resource Sharing (CORS) - это механизм, который позволяет браузерам делать запросы к источникам, отличным от того, который предоставил веб-страницу. Вот правильный процесс:
- SiteA предоставляет клиенту код JavaScript
- Код JavaScript делает запрос к siteB
- Сервер siteB отвечает с соответствующими заголовками CORS
- Браузер проверяет, разрешен ли источник
- Если разрешен, браузер делает ответ доступным для кода JavaScript
Согласно Wikipedia, “При выполнении определенных типов междоменных Ajax-запросов современные браузеры, поддерживающие CORS, инициируют дополнительный ‘предварительный’ запрос (preflight), чтобы определить, есть ли у них разрешение на выполнение действия.”
Браузер автоматически обрабатывает проверки безопасности CORS. Если siteB не включает правильный заголовок Access-Control-Allow-Origin, браузер заблокирует доступ к ответу для кода JavaScript, даже если HTTP-запрос был успешным.
Предварительные запросы и метод OPTIONS
Для определенных типов запросов браузеры отправляют предварительный запрос (preflight) с использованием метода OPTIONS перед отправкой фактического запроса. Это происходит, когда:
- Запрос использует методы, отличные от GET, HEAD или POST
- Запрос использует POST с Content-Type, отличным от
application/x-www-form-urlencoded,multipart/form-dataилиtext/plain - Запрос включает пользовательские заголовки
Предварительный запрос включает заголовки, такие как:
Origin: Запрашиваемый источникAccess-Control-Request-Method: Используемый HTTP-методAccess-Control-Request-Headers: (Опционально) Пользовательские заголовки, которые будут отправлены
Сервер должен отвечать на предварительные запросы заголовками, указывающими, что разрешено:
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Как объясняется в PortSwigger Web Security Academy, “При получении CORS-запроса предоставленный источник сравнивается с белым списком. Если источник присутствует в белом списке, он отражается в заголовке Access-Control-Allow-Origin, и предоставляется доступ.”
Правильные шаги по реализации
Чтобы включить доступ JavaScript из siteA к ресурсам на siteB:
1. Настройте ответы сервера siteB
SiteB должен включать соответствующие заголовки CORS в свои HTTP-ответы:
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
2. Обрабатывайте предварительные запросы
SiteB должен отвечать на запросы OPTIONS с соответствующими заголовками:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
3. Специальные случаи для учетных данных
Если вам нужно отправлять куки или заголовки аутентификации, вы должны:
- Включить
Access-Control-Allow-Credentials: true - НЕ использовать подстановочный знак (
*) дляAccess-Control-Allow-Origin - Указать точный источник
Пример:
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Credentials: true
4. Примеры реализации на стороне сервера
Вот реализации для разных типов серверов:
Node.js Express
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://siteA');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
app.options('*', (req, res) => {
res.header('Access-Control-Allow-Origin', 'http://siteA');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.send();
});
Конфигурация Nginx
server {
listen 80;
server_name siteB.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'http://siteA';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'http://siteA' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
# Ваша существующая конфигурация
}
}
Распространенные ошибки и решения
Ошибка 1: Установка заголовка на неправильном сервере
Проблема: Установка Access-Control-Allow-Origin на siteA вместо siteB
Решение: Заголовок должен присутствовать в ответах от siteB, запрашиваемого сервера
Ошибка 2: Использование подстановочного знака с учетными данными
Проблема: Использование Access-Control-Allow-Origin: * при отправке куков или заголовков аутентификации
Решение: Используйте конкретные источники при работе с учетными данными
Ошибка 3: Отсутствие обработки предварительных запросов
Проблема: Отсутствие ответов на запросы OPTIONS
Решение: Реализуйте обработчик OPTIONS на siteB
Ошибка 4: Запросы из File://
Проблема: CORS не работает при открытии HTML-файлов напрямую с диска
Решение: Используйте локальный сервер во время разработки
Вопросы безопасности
При реализации CORS учитывайте эти лучшие практики безопасности:
- Принцип наименьших привилегий: Разрешайте доступ только тем источникам, которые в этом нуждаются
- Избегайте подстановочного знака для чувствительных данных: Никогда не используйте
*для API, которые обрабатывают конфиденциальную информацию - Проверяйте источники: Убедитесь, что источник в запросах соответствует ожидаемому
- Учитывайте временные ограничения: Устанавливайте подходящие значения
Access-Control-Max-Ageдля кэширования предварительных запросов - Мониторьте злоупотребления: Ведите журналы CORS-запросов для обнаружения потенциальных проблем безопасности
Согласно PortSwigger, “сервер другого домена может разрешить чтение ответа при передаче ему учетных данных, установив заголовок CORS Access-Control-Allow-Credentials в true.”
Источники
- Cross-Origin Resource Sharing (CORS) - HTTP | MDN
- Заголовок Access-Control-Allow-Origin - HTTP | MDN
- Cross-origin resource sharing - Wikipedia
- CORS и заголовок ответа Access-Control-Allow-Origin | Web Security Academy
- Включение междоменных запросов (CORS) в ASP.NET Core | Microsoft Learn
- CORS, предварительный запрос и метод OPTIONS - DEV Community
- Понимание CORS и предварительных запросов в API | Medium
Заключение
Чтобы правильно включить доступ JavaScript из siteA к ресурсам на siteB с использованием CORS:
- Настройте siteB на включение
Access-Control-Allow-Origin: http://siteAв его ответы - Обрабатывайте предварительные запросы с правильными ответами метода OPTIONS
- Избегайте использования подстановочного знака (*) при работе с учетными данными
- Тщательно тестируйте с разными типами запросов и заголовками
- Следуйте лучшим практикам безопасности для поддержания правильного контроля доступа
Ваш первоначальный подход был неправильным, потому что вы устанавливали заголовок CORS на siteA вместо siteB. Помните, что запрашиваемый сервер (siteB) несет ответственность за указание, какие источники могут получить доступ к его ресурсам. Реализовав правильные заголовки на siteB, вы включите междоменные запросы, сохраняя безопасность через механизм принудительного выполнения CORS в браузере.