Совместное использование ресурсов между разными источниками (CORS)
Безопасный доступ к ресурсам на других доменах
Политика одинакового источника блокирует в браузере чтение ресурса из другого источника. Этот механизм не позволяет вредоносному сайту читать данные другого сайта, но он также препятствует правомерному применению. Что если вы захотите получить данные о погоде в другой стране?
В современном веб-приложении приложение часто хочет получать ресурсы из другого источника. Например, вы хотите получить данные JSON из другого домена или загрузить изображения с другого сайта в элемент <canvas>
.
Другими словами, существуют общедоступные ресурсы, которые должны быть доступны для чтения всем, но политика одинакового источника блокирует эту возможность. Разработчики использовали обходные пути, такие как JSONP, но благодаря механизму совместного использования ресурсов между разными источниками (CORS) браузер может предоставлять страницам доступ к ресурсам другого домена.
Включение CORS позволяет серверу сообщать браузеру, что ему разрешено использовать дополнительный источник.
Как работает запрос ресурса в Интернете? #

Браузер и сервер могут обмениваться данными по сети с помощью протокола передачи гипертекста (HTTP). HTTP определяет правила связи между запрашивающей стороной и респондентом, включая информацию, необходимую для получения ресурса.
HTTP-заголовок используется для согласования типа обмена сообщениями между клиентом и сервером и применяется для определения доступа. И запрос браузера, и ответное сообщение сервера разделены на две части: header (заголовок) и body (тело):
header #
Информация о сообщении, такая как тип сообщения или кодировка сообщения. Заголовок может включать в себя различные сведения, выраженные парами «ключ-значение». Заголовок запроса и заголовок ответа содержат разную информацию.
Пример заголовка запроса
Accept: text/html
Cookie: Version=1
Вышеупомянутое эквивалентно высказыванию: «Я хочу получить в ответ HTML. Вот файл cookie, который у меня есть».
Пример заголовка ответа
Content-Encoding: gzip
Cache-Control: no-store
Вышеупомянутое эквивалентно высказыванию: «Данные закодированы с помощью gzip. Не кешируйте это, пожалуйста».
body #
Само сообщение. Это может быть обычный текст, двоичное изображение, JSON, HTML и т. д.
Как работает CORS? #
Помните, что политика одинакового источника предписывает браузеру блокировать запросы между разными источниками. Если вы хотите получить общедоступный ресурс из другого источника, сервер, предоставляющий ресурсы, должен сообщить браузеру: «Данный источник запроса может получить доступ к моему ресурсу». Браузер запоминает это и разрешает совместное использование ресурсов между разными источниками.
Шаг 1: запрос клиента (браузера) #
Когда браузер делает запрос на другой источник, он добавляет заголовок Origin
с текущим источником (схема, хост и порт).
Шаг 2: ответ сервера #
На стороне сервера, когда сервер видит этот заголовок и хочет разрешить доступ, он должен добавить к ответу заголовок Access-Control-Allow-Origin
с указанием источника запроса (или *
для разрешения любого источника).
Шаг 3: браузер получает ответ #
Когда браузер видит этот ответ с соответствующим заголовком Access-Control-Allow-Origin
, браузер разрешает передачу данных ответа сайту клиента.
CORS в действии #
Вот крошечный веб-сервер, использующий Express.
Первая конечная точка (строка 8) не имеет установленного заголовка ответа, она просто отправляет файл в ответ.
Откройте DevTools, нажав
Control+Shift+J
(илиCommand+Option+J
, если у вас Mac). - Откройте DevTools, нажавControl+Shift+J
(илиCommand+Option+J
, если у вас Mac).Перейдите на вкладку Консоль.
Попробуйте следующую команду:
fetch('https://cors-demo.glitch.me/', {mode:'cors'})
Вы должны увидеть сообщение об ошибке:
request has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
is present on the requested resource.
Вторая конечная точка (строка 13) отправляет тот же файл в ответ, но добавляет в заголовок Access-Control-Allow-Origin: *
. С консоли наберите:
fetch('https://cors-demo.glitch.me/allow-cors', {mode:'cors'})
На этот раз ваш запрос не должен блокироваться.
Учетные данные и CORS #
По соображениям конфиденциальности CORS обычно используется для «анонимных запросов», когда запрос не идентифицирует отправителя. Если вы хотите при использовании CORS отправлять файлы cookie (которые могут идентифицировать отправителя), необходимо добавить дополнительные заголовки к запросу и ответу.
Запрос #
Добавьте credentials: 'include'
в параметры выборки, как показано ниже. Это включит файл cookie в запрос.
fetch('https://example.com', {
mode: 'cors',
credentials: 'include'
})
Ответ #
Для заголовка Access-Control-Allow-Origin
необходимо указать конкретный источник (без подстановочного знака *
), а для параметра Access-Control-Allow-Credentials
следует установить значение true
.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Предварительные запросы для сложных HTTP-вызовов #
Если веб-приложению требуется сложный HTTP-запрос, браузер добавляет предварительный запрос в начало цепочки запросов.
Спецификация CORS определяет сложный запрос как
- запрос, использующий методы, отличные от GET, POST или HEAD;
- запрос, который включает заголовки, отличные от
Accept
,Accept-Language
илиContent-Language
; - запрос с
Content-Type
отличным отapplication/x-www-form-urlencoded
,multipart/form-data
илиtext/plain
.
Браузеры создают предварительный запрос, если это необходимо. Ниже показан запрос OPTIONS
, который отправляется перед фактическим сообщением- запросом.
OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: DELETE
На стороне сервера приложение должно ответить на предварительный запрос информацией о методах, которые приложение принимает от этого источника.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, DELETE, HEAD, OPTIONS
Ответ сервера может также включать заголовок Access-Control-Max-Age
для указания продолжительности кеширования результатов предварительного запроса (в секундах), чтобы клиенту не нужно было делать предварительный запрос каждый раз при отправке сложного запроса.