Простые и предварительные запросы, обработка ошибок CORS

Cross-Origin Resource Sharing (CORS) — это протокол, который позволяет сценариям, запущенным в клиенте браузера, взаимодействовать с ресурсами из другого источника. Это полезно, потому что благодаря политике того же происхождения, за которой следуют XMLHttpRequest и fetch, JavaScript может выполнять вызовы только к URL-адресам, которые находятся в том же источнике, что и место, где выполняется скрипт.

Например, если приложение JavaScript хочет сделать вызов AJAX к API, работающему на другом порту, оно будет заблокировано благодаря политике того же источника. Ошибка CORS в консоли будет выглядеть так:

КОРС — зачем это нужно?

В большинстве случаев скрипту, работающему в браузере пользователя, требуется доступ только к ресурсам из того же источника (подумайте о вызовах API к тому же бэкенду, который изначально обслуживал код JavaScript). Таким образом, тот факт, что JavaScript обычно не может получить доступ к ресурсам из других источников, является хорошим фактором для безопасности.

Политика одного и того же источника борется с одной из самых распространенных кибератак: Подделка межсайтовых запросов CSRF). В этом маневре вредоносный веб-сайт пытается воспользоваться системой хранения файлов cookie браузера.

В этом контексте «другие источники» означают, что URL-адрес, к которому осуществляется доступ, отличается от местоположения, из которого выполняется JavaScript, имея:

  • другая схема (HTTP или HTTPS)
  • другой домен
  • другой субдомен
  • другой порт

Например:
Давным-давно, совместное использование ресурсов с https://ayushv.medium.com сдругим доменом (google.com/api/getdata ), другой субдомен
(api.ayushv.medium.com), другой порт (ayushv.medium.com:5050), разные протоколы (http://ayushv.medium.com) не разрешались. После того, как CORS стал стандартом, браузеры позволяют все это.

Однако существуют законные сценарии, в которых желателен или даже необходим доступ из разных источников. Например, если вы используете React SPA, который обращается к серверной части API, работающей в другом домене. Веб-шрифты также полагаются на CORS для работы. CORS представляет стандартный механизм, который может использоваться всеми браузерами для реализации междоменных запросов. Спецификация определяет набор заголовков, которые позволяют браузеру и серверу сообщать о том, какие запросы разрешены (а какие нет). CORS продолжает дух открытой сети, предоставляя доступ к API для всех.

Идентификация ответа CORS

Когда сервер правильно настроен для совместного использования ресурсов между источниками, будут включены некоторые специальные заголовки. По их наличию можно определить, поддерживает ли запрос CORS. Веб-браузеры могут использовать эти заголовки, чтобы определить, следует ли продолжить вызов XMLHttpRequest или нет.

Есть несколько заголовков, которые можно установить, но основной, определяющий, кто может получить доступ к ресурсу, — Access-Control-Allow-Origin. Этот заголовок указывает, какие источники могут получить доступ к ресурсу. Например, чтобы разрешить доступ из любого источника, вы можете установить этот заголовок следующим образом:

Access-Control-Allow-Origin: *

Или его можно сузить до определенного происхождения:

Access-Control-Allow-Origin: https://example.com

Общие сведения о типах запросов CORS

Существует два типа запросов CORS: простые запросы и предварительные запросы, и именно браузер определяет, какой из них используется. Как разработчику, вам обычно не нужно заботиться об этом, когда вы создаете запросы для отправки на сервер. Однако вы можете увидеть в сетевом журнале различные типы запросов, и, поскольку это может повлиять на производительность вашего приложения, полезно знать, почему и когда эти запросы отправляются.

Давайте посмотрим, что это значит более подробно в следующих двух разделах.

Простые запросы (GET, POST и HEAD)

Браузер считает запрос простым запросом, когда сам запрос соответствует определенному набору требований:

  • Используется один из этих методов: GET, POST или HEAD
  • Используется заголовок из безопасного списка CORS ( Accept, Accept-Language and Content-Language)
  • При использовании заголовка Content-Type допускаются только следующие значения: application/x-www-form-urlencoded, multipart/form-data или text/plain.
  • Ни один прослушиватель событий не зарегистрирован ни в одном объекте XMLHttpRequestUpload.
  • В запросе не используется объект ReadableStream

Запрос может продолжаться как обычно, если он соответствует этим критериям, а заголовок Access-Control-Allow-Origin проверяется при возврате ответа.

Предварительные запросы (ВАРИАНТЫ)

Если запрос не соответствует критериям простого запроса, вместо этого браузер выполнит автоматический предварительный запрос с использованием метода OPTIONS. Этот предварительный запрос выполняется некоторыми браузерами в качестве меры безопасности, чтобы убедиться, что сервер доверяет выполняемому запросу. Это означает, что сервер понимает, что метод, источник и заголовки, отправляемые в запросе, безопасны для выполнения действий.
Этот вызов используется для определения точных возможностей CORS сервера, которые, в свою очередь, используются для определения того, не понятен предполагаемый протокол CORS. Если результат вызова OPTIONS говорит о том, что запрос не может быть выполнен, фактический запрос к серверу выполняться не будет.

Запрос предварительной проверки устанавливает режим OPTIONS и устанавливает пару заголовков для описания фактического запроса, который должен следовать:

  • Access-Control-Request-Method: предполагаемый метод запроса (например, GET или POST).
  • Access-Control-Request-Headers: указание пользовательских заголовков, которые будут отправлены с запросом.
  • Origin: обычный заголовок источника, который содержит текущий источник сценария.

Пример такого запроса может выглядеть так:

# Request
curl -i -X OPTIONS localhost:3001/api/ping \
-H 'Access-Control-Request-Method: GET' \
-H 'Access-Control-Request-Headers: Content-Type, Accept' \
-H 'Origin: http://localhost:3000'

Этот запрос в основном говорит: «Я хотел бы сделать запрос GET с заголовками Content-Type и Accept с http://localhost:3000 — возможно ли это?».

Сервер будет включать в ответ несколько заголовков Access-Control-*, чтобы указать, будет ли разрешен следующий запрос или нет. К ним относятся:

  • Access-Control-Allow-Origin: источник, которому разрешено делать запрос, или *, если запрос может быть сделан из любого источника.
  • Access-Control-Allow-Methods: список разрешенных HTTP-методов, разделенных запятыми.
  • Access-Control-Allow-Headers: разделенный запятыми список настраиваемых заголовков, которые разрешено отправлять. Если вы используете настраиваемые заголовки (например, x-authentication-token, вам необходимо вернуть его в этом ответе заголовка ACA на вызов OPTIONS, в противном случае запрос будет заблокирован.
  • Access-Control-Expose-Headers: Точно так же этот ответ должен содержать список заголовков, которые будут присутствовать в фактическом ответе на вызов и должны быть доступны клиенту. Все остальные заголовки будут ограничены.
  • Access-Control-Allow-Credentials: Этот заголовок должен присутствовать в ответе только в том случае, если ваш сервер поддерживает аутентификацию с помощью файлов cookie. Единственным допустимым значением для этого случая является true.
  • Access-Control-Max-Age: максимальная продолжительность, в течение которой ответ на предварительный запрос может кэшироваться до того, как будет сделан другой вызов.

Затем ответ будет проверен браузером, чтобы решить, продолжать ли запрос или отказаться от него.

Таким образом, ответ на предыдущий пример может выглядеть так:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Vary: Access-Control-Request-Headers
Access-Control-Allow-Headers: Content-Type, Accept
Content-Length: 0
Date: Fri, 05 Apr 2019 11:41:08 GMT
Connection: keep-alive

Заголовок Access-Control-Allow-Origin в этом случае позволяет сделать запрос из любого источника, а заголовок Access-Control-Allow-Methods описывает только принятые HTTP-методы. Если данный метод HTTP не принят, он не будет отображаться в этом списке.

В этом примере Access-Control-Allow-Headers возвращает заголовки, которые были запрошены в запросе OPTIONS. Это указывает на то, что разрешена отправка всех запрошенных заголовков. Если, например, сервер не разрешает заголовок Accept, то этот заголовок будет исключен из ответа, и браузер отклонит вызов.

Обработка ошибки CORS

Лучший способ — следовать стандартам CORS и определить набор заголовков, которые позволяют браузеру и серверу сообщать о том, какие запросы разрешены (а какие нет). Это наилучший сценарий — вы сможете реализовать правильный ответ CORS на сервере, к которому вы обращаетесь. Если API использует express для узла, вы можете использовать простой пакет cors. Если вы хотите сделать свой сайт должным образом безопасным, рассмотрите возможность использования белого списка для заголовка Access-Control-Allow-Origin.

Для локальной разработки есть несколько хаков:

  • Плагины для хрома, с помощью которых браузер обходит все фильтры CORS.




  • Еще один способ — запустить Chrome с флагом disable-web-security. Этот конкретный веб-сеанс будет без CORS. На самом деле это обход веб-безопасности, который есть в Chrome.
chrome --disable-web-security --user-data-dir

Подробнее о КОРС

Если вы хотите узнать больше о деталях CORS, я рекомендую ознакомиться с подробной статьей MDN.

Я надеюсь, что вы нашли это полезным. Спасибо за чтение :)