Руководство по разработчике по политике с той же оригинальной политикой (SOP) и обмену ресурсами межоригина (CORS)

Руководство по разработчике по политике с той же оригинальной политикой (SOP) и обмену ресурсами межоригина (CORS)

15 июля 2025 г.

Если вы потратили достаточно времени на разработку веб -приложений, вы, безусловно, столкнулись с этой ошибкой:

Access to fetch at 'https://api.example.com' from origin 'https://app.yoursaas.com' has been blocked by CORS

Звучит знакомо?

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

Эта ошибка возникает из-за механизма безопасности, закрепленного браузером, называетсяПолитика в одно и той жежигин (SOP), который блокирует доступ JavaScript к корпусу ответа перекрестных HTTP-запросов по умолчанию.

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

ОбаSOPиКоррпредназначены для защиты пользователей путем предотвращения несанкционированного доступа к ресурсам и конфиденциальных данных между различным происхождением. Как разработчик, понимание того, как они работают вместе, необходимо для создания безопасных и надежных веб -приложений. Но сначала:

Что такое «происхождение»?

Анонцаисточникопределяется тремя вещами:

  • Протокол (httpВhttps)
  • Доменное имя (example.com)
  • Порт (:80В:443, и т. д.)

Комбинация этих трех: протокол, домен и порт определяет происхождение.

Diagram explaining the concept of origin in web security. Shows how protocol (https), domain (example.com), and port (443) combine to form an origin, with examples of different origins being blocked due to changes in protocol or subdomain.

Если кто -то из них отличается, происхождениене то же самое:

https://app.example.com  ≠  http://app.example.com
https://app.example.com  ≠  https://api.example.com
https://example.com:3000 ≠  https://example.com

Итак, теперь, когда мы дали понять, что такое происхождение, мы можем добраться до мяса этого поста.

Какая такая же политика (SOP)?

В соответствии сМДН:

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

Источник: MDN Web Docs, лицензированный подCC-BY-SA 2.5Полем

Политика одинаковой теоригины является одним из краеугольных камней веб-безопасности. Он защищает ваших пользователей, останавливая потенциально вредоносные веб -сайты взаимодействовать с данными вашего сайта за спиной.

На простом английском:Браузеры разрешают веб -страницам отправлять запросы на различное происхождение, но блокируют JavaScript от доступа к ответу, если сервер явно не допускает его через обмен ресурсами кроссоригина.

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

Это поднимает еще одну общую точку путаницы при изучении SOP и CORS:

Некоторые перекрестные запросыразрешен по дизайнуПолем Вот основные исключения:

  • <a href="https://example.com">: Ссылка на другие сайты допускается.
  • <img src="https://cdn.example.com/logo.png">: Изображения могут быть загружены поперечным происхождением.
  • <script src="https://unpkg.com/vue">: Сценарии из CDN разрешены (но ограничены черезCSP)
  • Формировать представления (<form action="https://api.example.com">) также разрешены.

Что общего, так это то, чтоОни не дают JavaScript доступ к корпусу ответаи это ключ.

Напротив, когда вы используете что -то вродеfetch()илиXMLHttpRequest, ты пытаешьсяПрочитайте данные ответа непосредственно из другого происхожденияИ вот где SOP рисует линию.

Таким образом, в то время как некоторые перекрестные запросы разрешены для функциональности или рендеринга, браузер сохраняет строгую границу:Если ваш JavaScript хочет взаимодействовать с данными ответа, вам понадобится разрешение. Вот гдеКоррвмешивается.

Примечание безопасности:SOP не защищает от перекрестных запросов на подделку для запросов, таких как подачи формы. Они разрешены перекрестные запросы и не полагаются на JavaScript, поэтому SOP и COR не применяются. CSRF работает именно потому, что браузер автоматически включает в себя учетные данные, такие как файлы cookie по таким запросам. Для «простых» запросов (мы доберемся до этого позже), которые используют JavaScript, SOP может блокировать чтение ответа, но сервер по -прежнему обрабатывает запрос, если не существует надлежащей защиты CSRF.

Итак, как мы получаем доступ к данным межоригина, когда нам это нужно?

Введите CORS: совместное использование ресурсов по перекрестному происхождению

Итак, что произойдет, если ваш фронтендпотребностипоговорить с другим происхождением, как API, размещенный в другом месте?

Вот гдеКоррвходит.

Обмен ресурсами по перекрестному происхождению-это стандарт безопасности, который сообщает браузерам, какие перекрестные запросы разрешены. По сути, это механизм исключений в SOP.

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

Вот как это работает: сервер отвечает вместе с конкретными заголовками CORS, и на основе значений, которые содержат эти заголовки, браузер решает, предоставить ли JavaScript доступ к корпусу ответа или блокировать его.

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

Простой пример CORS

Допустим, вы пытаетесь получить пользовательские данные из вашего API с помощью простого httpGETзапрос:

fetch("https://backend.com/some-api")
.then(res => res.json())
.then(data => console.log(data));

Когда браузер обнаруживает, что запрос идет на другое происхождение, он отправляет его и ждет ответа сервера. Тогда он проверяет, есть лиresponse from the serverВключаетAccess-Control-Allow-Originзаголовок. Если заголовок отсутствует или не разрешает запрашивающее происхождение, браузер блокирует JavaScript доступа к ответу.

Access-Control-Allow-Originэто заголовок CORS, который указывает, какое удаленное происхождение разрешено делать запросы на сервер. Его ценность может быть подстановочным знаком*, что означает, что все происхождение разрешено (не рекомендуется для целей безопасности) или конкретный домен. Чтобы поддержать несколько доменов, вам понадобится логика на стороне сервера, чтобы динамически установить значение для заголовка.

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

Вот пример заголовков ответов нашего предыдущего запроса API на получение пользовательских данных:

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

В нашем случае,Access-Control-Allow-OriginЗаголовок говорит нам, что только происхождениеhttps://frontend.comразрешено сделать перекрестный запрос на сервер, который является источником нашего фронта, поэтому мы можем безопасно получить доступ к ответу, и никакие ошибки не подняты.

О «простых запросах» и «сложных запросах»

Прежде чем мы движемся дальше, есть один ключевой нюанс, чтобы понять: браузеры классифицируют HTTP -запросы как «простые» или «сложные».

Для просьбы считаться «простым», он должен соответствовать следующим критериям:

  • ТолькоGETВHEAD, илиPOSTметод
  • Нет пользовательских заголовков, таких какAuthorizationВX-Custom-Header, илиAccept-EncodingПолем ТолькоAcceptВAccept-LanguageВContent-Language, иContent-Type(С допустимыми значениями см. Следующие критерии).
  • АContent-TypeЗначение заголовкаtext/plainВmultipart/form-dataВapplication/x-www-form-urlencodedПолем

Если запрос не соответствует ни одному из вышеуказанных условий, браузер рассмотрит его «сложным». Например, HTTPPUTзапрос илиPOSTзапрос сContent-Type: application/jsonзаголовок.

В предыдущем примере запросGETбез индивидуальных заголовков, и нетContent-Type, поэтому это квалифицируется как простой запрос.

Зачем нам это различие?

ДляПростые запросы, браузер ищет толькоAccess-Control-Allow-OriginЗаголовок в ответе сервера. Если он присутствует и действителен, браузер предоставляет доступ JavaScript к корпусу ответа. Это именно то, что происходит в нашем предыдущем примере.

Сложные запросы, однако, требуют большего внимания. Браузер ожидает дополнительных заголовков CORS, и еще до того, как он отправит фактический запрос, он сначала выпускает автоматическийПредварительный запрос, отдельныйOPTIONSЗапрос, чтобы проверить, разрешен ли реальный запрос. Только если сервер правильно отреагирует на предварительное поле, браузер будет продолжаться с фактическим запросом.

До сих пор мы говорили только о «простых» запросах, но для сложных запросов необходим запрос на предварительную работу, прежде чем произойдет что -либо еще.

До сих пор мы имели дело только с простыми запросами. Но когда дело доходит до сложных запросов, все меняется; Браузер должен сначала отправить запрос на предварительную работу, чтобы убедиться, что сервер разрешает предполагаемое действие до отправки фактического запроса.

Как выглядит запрос на предварительную полета?

Так:

OPTIONS /user HTTP/1.1
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization

Эти два заголовка,Access-Control-Request-Method, иAccess-Control-Request-Headers, сыграйте ключевую роль в информировании сервера о том, чего ожидать. Я подробно объясняю оба вмой другой пост о запросахПолем

Затем сервер должен ответить заголовками, которые явно разрешают запрос:

Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization

Только тогда браузер продолжается с фактическим запросом.

Если сервер предоставляет допустимый ответ, браузер продолжается с фактическим запросом. Однако, если какой -либо из этих заголовков отсутствует или неверно, браузер будетблокироватьЗапрос еще не произойдет.

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

Два ключевых вывода

  • Предварительные запросы являются частью COR, а не SOP.Если сервер не отвечает правильноOPTIONS requestФактический запрос никогда не отправляется. На данный момент нет роли для СОП; Поскольку ответа нет, браузеру не нужно блокировать доступ ни к чему.
  • Для сложных запросов сервер должен ответить дополнительными заголовками, не простоAccess-Control-Allow-OriginПолем К ним относятсяAccess-Control-Allow-MethodsВAccess-Control-Allow-HeadersПолем Я также подробно объяснил их в своем предыдущем посте о предварительных запросах, на которые я настоятельно рекомендую вам взглянуть.

Почему это важно

Этот дополнительный предварительный шаг добавляет безопасность и прозрачность.

  • Серверы могут контролироватьВОЗразрешено получить доступ к ним.
  • Они могут ограничитьКакие методы и заголовкипринимаются.
  • Они могут полностью заблокировать определенные приложения, опустив заголовки CORS.

Например, если кто -то пытается позвонить в ваш API из другого приложения, которому вам не доверяет, запрос будетзаблокировано полностьюПолем Это возможно, потому что браузер автоматически включаетOriginЗаголовок в перекрестных запросах, сообщая сервер узнать, откуда взялся запрос. Основываясь на этом происхождении, сервер может решить, отвечать ли отвечать с необходимыми заголовками CORS или нет.

Какой смысл запросов на предварительный полевой полет, если SOP уже существует?

Проще говоря, потому что одного SOP недостаточно.

SOP не останавливает запрос, который будет иметь место, он блокирует JavaScript от чтения ответа, если он перекрестный происхождение без надлежащих заголовков CORS.

Но предварительные запросы случаютсядоРеальный запрос отправляется, если браузер считает его потенциально «небезопасным или сложным», например, если запрос использует пользовательские заголовки, такие методы, какPUTилиDELETEПолем Это в основном браузер, просящий сервер для разрешения отправить фактический запрос.

Запросы SOP и предварительно полезно предлагают различные уровни защиты. Позвольте мне проиллюстрировать это с примером:

Представьте себе этот сценарий:

fetch('https://backend.com/delete-account', {
  method: 'DELETE',
});

Без предварительного полета CORS, браузер может отправить фактическиеDELETEзапрос, и хотя JavaScript не мог прочитать ответ из -за SOP,Ущерб уже нанесен, учетная запись удалена.

SOP только защищаетответ, незапросПолем

3 важные вещи, которые следует иметь в виду

1. Не используйте*в производстве

С использованиемAccess-Control-Allow-Origin: *разрешаетлюбой сайтЧтобы получить доступ к вашему API. Это нормально для публики, только для чтения API, но плохо для аутентифицированных или конфиденциальных маршрутов.

Будьте специфическими в производстве, чтобы избежать рисков безопасности.

2. ДобавитьAccess-Control-Allow-Credentials: trueСcredentials: 'include'

Если вы собираетесь отправлять файлы cookie вместе с запросом, таким образом:

fetch("https://backend.com/post-api", {
  credentials: "include"
})

Тогда ваш бэкэнд должен включатьAccess-Control-Allow-Credentials: trueв своем ответе. В противном случае браузер заблокирует ответ, даже если источник разрешено.

Кроме того, вы должны быть конкретными при объявлении разрешенного происхождения вAccess-Control-Allow-Origin, что означает*больше не принимается как значение. Браузер примет только ответ сAccess-Control-Allow-OriginУстановите одно конкретное происхождение, если запрос отправлен с учетными данными.

3. SOP и CORS только защищают браузер

Политика в одно и то же теорегин и CORОБЪЕДИНЯЕТ БРАУЗЕРМеханизмы безопасности. Они не защищают ваш сервер от вредоносных запросов, сделанных такими инструментами, какcurlПочтальон или индивидуальные сценарии.

Чтобы полностью защитить ваши API, вам нужны защита на стороне сервера, например:

  • Проверки аутентификации и авторизации.
  • Валидация ключей API.
  • Ограничение дросселя и ограничения скоростиПолем
  • Проверка ввода и дезинфекцияПолем

Общие ошибки CORS (и что они означают)

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

Причина: заголовок CORS 'Отсутствует

Это означает, чтоAccess-Control-Allow-OriginЗаголовок полностью отсутствует в ответе. Сервер вообще не включал его. Вам нужно настроить свой сервер, чтобы включить заголовок в его ответы.

Причина: заголовок CORS 'Access-Control-Allow-Origin' не соответствует 'https://frontend.com'

Происхождение "https://frontend.com" не разрешается делать перекрестные запросы на сервер. Чтобы исправить это, сделайте "https://frontend.com"Access-Control-Allow-OriginЗаголовок на сервере.

Причина: учетные данные не поддерживаются, если заголовок CORS 'Access-Control-Allow-Origin' IS*'

Вы попытались сделать запрос с учетными данными (куки, заголовки аутентификации HTTP), ноAccess-Control-Allow-Originустановлен на*, который запрещает использование учетных данных. Чтобы исправить это, либо пропуститьcredentials: "include"Если вы работаете сfetch(), или настроить конкретное происхождение как значениеAccess-Control-Allow-OriginПолем

Причина: не нашел метод в заголовке CORS 'Access-Control-Allow-Methods'

Вы отправили перекрестный запрос с методом, который не поддерживается сервером. Например, вы сделалиDELETEзапрос, но возвращенный ответ включенAccess-Control-Allow-Methods: GET, POST, HEAD, PATCHПолем Как вы видете,DELETEне является одним из методов, разрешенных в перекрестном происхождении.

Причина: несколько заголовок CORS 'Access-Control-Allow-Origin' не допускается

Это происходит, когда сервер отвечает более чем однимAccess-Control-Allow-OriginЗаголовок или один заголовок с разделенным запятыми списком происхождения, который браузеры не принимают. Вы должны вернуть только одно происхождение в качестве значения или использовать подстановку*Чтобы позволить всем происхождениям (следует избегать в производстве).

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

Как починить cors (в Express.js)

Вы исправляете CORS насервер, не фронт.

Я рекомендую использоватьcorsБиблиотека для обработки перекрестного обмена ресурсами. Во -первых, вам нужно установить черезNpm, или ваш предпочтительный менеджер пакетов:

npm install cors

И теперь вы можете использовать его в качестве промежуточного программного обеспечения в своем коде:

const cors = require("cors");
app.use(cors({ origin: "https://frontend.com" }));

corsБиблиотека уже обрабатываетOPTIONSзапросы из коробки.

Другие параметры конфигурации дляcorsпромежуточное программное обеспечение включает в себя:

  • methods: устанавливаетAccess-Control-Allow-Methodsзаголовок. Он принимает либо строку, выполненную с запятой"GET,PUT,POST", или массив, как["GET", "PUT", "POST"]Полем
  • credentials: устанавливаетAccess-Control-Allow-Credentialsзаголовок. Если установленоtrueЗаголовок будет передаваться, иначе он будет опущен.
  • allowedHeaders: устанавливаетAccess-Control-Allow-Headersзаголовок. Он принимает либо строку, выполненную с запятой"Content-Type,Authorization", или массив, как["Content-Type", "Authorization"]Полем

Резюме: как браузеры обрабатывают перекрестные запросы

Для простых запросов:Если запрос используетGETВPOST, илиHEAD, без пользовательских заголовков и безопасныхContent-Type, браузер немедленно отправляет запрос. Затем проверяет ответ на действительныйAccess-Control-Allow-Originзаголовок. Если присутствовать и исправить, JavaScript предоставляется доступ к ответу. Никакого предварительного полета не требуется.

Для сложных запросов:Если запрос включает в себя пользовательские заголовки (напримерAuthorization), использует такие методы, какPUTилиDELETE, или имеет нестандартныйContent-Type, браузер отправляет предварительный полеOPTIONSзапросить первое. Этот запрос включает в себяAccess-Control-Request-MethodиAccess-Control-Request-HeadersЗаголовки (оба объяснены в этой статье о запросах с предварительным полетом). Если сервер отвечает с правильнымAccess-Control-Allow-*Заголовки, браузер продолжается с фактическим запросом. В противном случае это блокирует это.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE