Каждый разработчик периодически сталкивается с огромной красной строкой в консоли — Access to fetched has been blocked by CORS policy
. Да, это здорово расстраивает. И хотя есть способы быстро избавиться от этой ошибки, давайте сегодня не будем принимать их как должное — лучше разберёмся, что на самом деле делает CORS и почему эта технология на самом деле наш друг.
В этой статье я не буду останавливаться на основах HTTP. Скажу лишь, что в своих примерах я использую HTTP/1.1, а не HTTP/2 — на CORS это никак не влияет.
Во фронтенде часто требуется отобразить данные, которые хранятся в другом месте. Перед этим браузер должен направить запрос серверу: клиент отправляет HTTP-запрос со всей информацией, которая нужна серверу, чтобы вернуть данные.
Представим, что нам надо получить информацию о пользователях для нашего сайта www.mywebsite.com
с сервера, который находится по адресу api.website.com
.
Сработало. Мы отправили HTTP-запрос на сервер, который вернул нужные нам данные в формате JSON. А теперь давайте попытаемся отправить точно такой же запрос, но с другого домена: вместо www.mywebsite.com
возьмём www.anotherdomain.com
.
Что произошло? Мы отправили такой же запрос, но на этот раз браузер выдал странную ошибку. Мы только что увидели CORS в действии. Давайте разберёмся, почему возникла эта ошибка, и что она означает.
В веб внедрено так называемое правило одинакового источника. По умолчанию мы можем получить доступ к ресурсам только в том случае, если источник этих ресурсов и источник запроса совпадают. К примеру, мы сможем без проблем загрузить изображение, которое находится по адресу https://mywebsite.com/image1.png
.
Ресурс считается принадлежащим к другому источнику (cross-origin), если он располагается на другом домене/поддомене, протоколе или порте.
Это, конечно, здорово, но для чего правило одинакового источника вообще существует?
Представим, что это правило не работает, а вы случайно нажали на какую-то вирусную ссылку, которую прислала ваша тётушка на Фейсбуке. Ссылка перенаправляет вас на мошеннический сайт, который с помощью фрейма загружает интерфейс сайта вашего банка и успешно залогинивает вас с помощью сохранённых куки.
Разработчики этого мошеннического сайта сделали так, чтобы он имел доступ к фрейму и мог взаимодействовать с DOM сайта вашего банка — так они смогут переводить деньги на свой счёт от вашего имени.
Да, это огромная угроза безопасности — мы ведь не хотим, чтобы кто угодно имел доступ к чему угодно.
К счастью, здесь приходит на помощь правило одинакового источника: оно гарантирует, что мы можем получить доступ только к ресурсам из того же самого источника.
В данном случае сайт www.evilwebsite.com
попытался получить доступ к ресурсам из другого источника —www.bank.com
. Правило одинакового источника заблокировало это действие. В результате разработчики мошеннического сайта не смогли добраться до нашей банковской информации.
Но какое отношение всё это имеет к CORS?
Несмотря на то, что правило одинакового источника применяется исключительно к скриптам, браузеры распространили его и на JavaScript-запросы: по умолчанию можно получить доступ к ресурсам только из одинакового источника.
Но нам ведь часто нужно обращаться к ресурсам из других источников… Может, тогда фронтенду стоит взаимодействовать с API на бэкенде, чтобы загружать данные? Чтобы обеспечить безопасность запросов к другим источникам, браузеры используют механизм под названием CORS.
Аббревиатура CORS расшифровывается как Cross-Origin Resource Sharing (Технология совместного использования ресурсов между разными источниками). Несмотря на то, что браузеры не позволяют получать доступ к ресурсам из разных источников, можно использовать CORS, чтобы внести небольшие коррективы в эти ограничения и при этом быть уверенным, что доступ будет безопасным.
Пользовательские агенты (к примеру, браузеры) на основе значений определённых заголовков для CORS в HTTP-запросе могут проводить запросы к другим источникам, которые без CORS были бы заблокированы.
Когда происходит запрос к другому источнику, клиент автоматически подставляет дополнительный заголовок Origin
в HTTP-запрос. Значение этого заголовка отражает источник запроса.
Чтобы браузер разрешил доступ к ресурсам из другого источника, он должен получить определённые заголовки в ответе от сервера, которые указывают, разрешает ли сервер запросы из других источников.
Разрабатывая бэкенд, мы, чтобы разрешить запросы из других источников, можем добавить в HTTP-ответ дополнительные заголовки, начинающиеся с Access-Control-*
. На основе значений этих CORS-заголовков браузер сможет разрешить определённые запросы из других источников, которые обычно блокируются правилом одинакового источника.
Существует несколько CORS-заголовков, но браузеру нужен всего один из них, чтобы разрешить доступ к ресурсам из разных источников —Access-Control-Allow-Origin
. Его значение определяет, из каких источников можно получить доступ к ресурсам на сервере.
Если мы создаём сервер, к которому должен иметь доступ сайт https://mywebsite.com
, то нужно внести этот домен в значение заголовка Access-Control-Allow-Origin
.
Отлично, теперь мы можем получать ресурсы из другого источника. А что будет, если мы попытаемся получить к ним доступ из источника, который не указан в заголовке Access-Control-Allow-Origin
?
Да, теперь CORS выдаёт эту печально известную ошибку, которая иногда всех нас так расстраивает. Но сейчас нам понятно, какой смысл она несет.
The 'Access-Control-Allow-Origin' header has a value 'https://www.mywebsite.com' that is not equal to the supplied origin.(Значение 'https://www.mywebsite.com' заголовка 'Access-Control-Allow-Origin' не совпадает с представленным источником)
В данном случае в качестве источника выступал сайт https://www.anotherwebsite.com
— его не было в списке разрешённых источников в заголовке Access-Control-Allow-Origin
. CORS успешно заблокировал запрос, и мы не можем получить доступ к запрашиваемым данным.
В качестве значения разрешённых источников CORS позволяет указать спецсимвол *
. Он означает, что доступ к ресурсам открыт из всех источников, поэтому используйте его с осторожностью.
Кроме Access-Control-Allow-Origin
, мы можем использовать и многие другие CORS-заголовки. Бэкенд-разработчик может изменить правила CORS на сервере так, чтобы разрешать/блокировать определённые запросы.
Ещё один довольно распространённый заголовок — Access-Control-Allow-Methods
. С ним будут разрешены только те запросы из других источников, которые выполнены с применением перечисленных методов.
В данном случае разрешены только запросы с методами GET
, POST
, или PUT
. Запросы с другими методами (например, PATCH
или DELETE
) будут блокироваться.
Если вам интересно почитать о других CORS-заголовках, ознакомьтесь с их списком на MDN.
С PUT
, PATCH
и DELETE
CORS работает с по-другому. В этих “непростых” случаях используются так называемые предварительные запросы (preflight requests).
Существует два типа CORS-запросов: простые и предварительные. Тип запроса зависит от хранящихся в нём значений (не волнуйтесь, здесь не надо будет ничего запоминать).
Запрос считается простым, если в нём используются методы GET
и POST
и нет никаких пользовательских заголовков. Любые другие запросы (например, с методами PUT
, PATCH
или DELETE
) — предварительные.
Если интересно узнать, каким требованиям должен соответствовать запрос, чтобы называться простым, почитайте эту статью на MDN.
Но что означают и почему существуют “предварительные запросы”?
Перед отправкой текущего запроса клиент сначала генерирует предварительный запрос: в своих заголовках Access-Control-Request-*
он содержит информацию о текущем запросе. Это позволяет серверу узнать метод, дополнительные заголовки и другие параметры запроса, который браузер пытается отправить.
Сервер получает этот предварительный запрос и отправляет обратно пустой HTTP-ответ с CORS-заголовками сервера. Браузер в свою очередь получает предварительный ответ (только CORS-заголовки) и проверяет, разрешён ли HTTP-запрос.
The request has an allowed origin - Источник запроса разрешёнThe request has an allowed method - Метод запроса разрешёнPreflight Response - Предварительный ответЕсли всё в порядке, браузер посылает текущий запрос на сервер, а тот в ответ присылает данные, которые мы запрашивали.
Если же возникает проблема, CORS блокирует предварительный запрос, а текущий вообще уже не будет отправлен. Предварительный запрос — отличный способ уберечь нас от получения доступа или изменения ресурсов на серверах, у которых (пока что) не настроены правила CORS. Сервера защищены от потенциально нежелательных запросов из других источников.
Чтобы уменьшить число обращений к серверу, можно кэшировать предварительные ответы, добавив к CORS-запросам заголовок Access-Control-Max-Age
. Так браузеру не придётся каждый раз отправлять новый предварительный запрос.
Куки, заголовки авторизации, TLS-сертификаты по умолчанию включены только в запросы из одинакового источника. Однако нам может понадобиться использовать учётные данные и в запросах из разных источников. Возможно, мы захотим включить куки в запрос, который сервер сможет использовать для идентификации пользователя.
В CORS по умолчанию отсутствуют учётные данные, но это можно изменить, добавив CORS-заголовок Access-Control-Allow-Credentials
.
Если необходимо включить куки и другие заголовки авторизации в запрос из другого источника, нужно установить значение true
в поле withCredentials
запроса, а также добавить в ответ заголовок Access-Control-Allow-Credentials
.
Готово — теперь мы можем включать учётные данные в запрос из другого источника.
Думаю, мы все согласимся с тем, что появление ошибок CORS порой расстраивает, но, тем не менее, здорово, что CORS позволяет безопасно отправлять запросы из разных источников в браузере — считаю, что мы должны любить эту технологию чуточку сильнее ????
Разумеется, о правиле одинакового источника и CORS можно рассказать гораздо больше, но я просто не смогу уместить всё это в одной статье. К счастью, ресурсов много (к примеру, спецификация W3) — вам будет к чему обратиться, если захотите подробнее изучить эту тему.
Перевод статьи Lydia Hallie: CS Visualized: CORS
Комментарии