
Руководство по разработчике по политике с той же оригинальной политикой (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
, и т. д.)
Комбинация этих трех: протокол, домен и порт определяет происхождение.
Если кто -то из них отличается, происхождениене то же самое:
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-*
Заголовки, браузер продолжается с фактическим запросом. В противном случае это блокирует это.
Оригинал