CORS – это механизм браузера, который позволяет серверам указывать сторонние источники, которые имеют право запрашивать у них ресурсы. Этот механизм обеспечивает безопасность и не дает вредоносным сайтам красть данные, которые принадлежат другим источникам.
CORS расшифровывается как Cross-Origin Resource Sharing, что переводится как «обмен ресурсами с запросом происхождения». В случае, когда для загрузки ресурса используется CORS, браузер отправляет предварительный HTTP-запрос OPTIONS. Сервер должен ответить, указав все источники, с которыми он собирается взаимодействовать. Также он может определить дополнительные ограничения, например, указать HTTP-заголовки, которые могут быть отправлены.
Браузер проверяет текущий источник и исходящий запрос на соответствие спецификациям сервера. Если все проверки были пройдены успешно, то запрос одобряется. В противном случае запрос будет отклонен. Если это произойдет, вы увидите предупреждение в консоли.
Когда используется CORS
Браузеры применяют CORS для запросов Ajax и Fetch. Этот механизм также используется для веб-шрифтов, текстур WebGL и отрисовки изображения холста с помощью drawImage(). CORS также потребуется для любого правомерного запроса к стороннему источнику.
CORS не применяется в том случае, если запрос рассматривается как «простой». Простой запрос должен начинаться с GET, HEAD или POST и иметь тип содержимого text/plain, application/x-www-form-urlencoded или multipart/form-data. Единственные заголовки простых запросов, которые допускаются, - это Accept, Accept-Language, Content-Language и Content-Type.
Если запрос не соответствует всем критериям, которые мы перечислили выше, то современные браузеры запускают CORS. Важно понимать, что CORS – это технология для браузера, и вы не сможете использовать его при самостоятельной отправке запросов, например, с помощью утилиты curl в своем терминале.
CORS не всегда отправляет предварительный запрос OPTIONS. Предварительная проверка нужна и используется только тогда, когда запрос может вызвать «побочные эффекты» на сервере. Как правило, это относится ко всем методам запроса, кроме GET.
Предположим, что есть запрос POST к/api/users/create. Сервер всегда будет создавать нового пользователя, но при этом браузер может отказать в доступе к ответу на этот запрос, если для запроса был использован CORS. Есть шанс, что сервер может отклонить реальный запрос, если перед этим был отправлен запрос OPTIONS. Это обеспечивает то, что учетная запись пользователя на самом деле не будет создаваться.
Управление CORS на стороне клиента
Несмотря на то, что CORS является технологией для браузера, вы все равно не можете влиять на нее напрямую с помощью клиентского кода. Это гарантирует, что вредоносные скрипты не смогут обойти защиту CORS, чтобы загрузить данные со сторонних доменов.
CORS, как правило, незаметен, поэтому вы даже не будете знать о том, что он работает. Если в процессе CORS произойдет сбой, то ваш код JavaScript увидит обычную сетевую ошибку. Получить точную информацию о том, что пошло не так, невозможно, поскольку это может представлять риск нарушения безопасности. Все подробности записываются в консоль.
Единственный способ устранить сбои CORS – это убедиться, что ваш сервер отправляет корректные заголовки ответов. Теперь давайте посмотрим, как это делается.
Управление CORS на стороне сервера
Для начала вам следует убедиться в том, что ваш сервер правильно обрабатывает запросы OPTIONS. Возможно, вам придется создать новый маршрут обработки запросов в вашей веб-среде. В большинстве случаев вам придется принимать запросы OPTIONS к каждой конечной точке, которая может получить CORS-запрос от браузера. Ответ не обязательно должен иметь тело, но он должен включать в себя определенные заголовки, которые сообщают браузеру, что делать дальше.
Начните с заголовка Access-Control-Allow-Origin. Он укажет на сторонний источник, который имеет право взаимодействовать с вашей конечной точкой. Указать можно только один источник; но вы можете обрабатывать несколько источников, динамически устанавливая в качестве значения заголовка источник, из которого был отправлен запрос. Текущий источник можно найти в заголовке запроса Origin.
Access-Control-Allow-Origin принимает * в качестве специального подстановочного символа. Это позволит принимать запросы CORS из всех источников. Здесь следует быть осторожным, поскольку указание разрешенных источников обеспечивает контроль и не дает вредоносным скриптам запрашивать данные с вашего сервера.
Access-Control-Allow-Origin должен быть включен в ответ вашего сервера на реальный запрос и в ответ на запрос OPTIONS. После того, как этот заголовок будет настроен, будет разрешен базовый обмен данными со сторонним клиентом браузера.
Указание CORS-заголовков
CORS-запросы, как правило, поддерживают только заголовки «простых» запросов, которые были перечислены выше. Если вы хотите использовать какой-то другой заголовок, например, Authorization или настраиваемый заголовок, то вашему серверу необходимо будет явно разрешить его в ответе на предварительный запрос.
Установите заголовок Access-Control-Allow-Headers. Его значение – это список названий заголовков через запятую, которые будут приняты с реальным запросом.
Access-Control-Allow-Headers: Authorization, X-Custom-Header
Теперь браузер разрешит запросы с заголовками Authorization или X-Custom-Header.
Браузер отправляет заголовок Access-Control-Allow-Headers вместе с предварительным CORS-запросом. Он содержит список заголовков, которые будут отправлены с реальным запросом. Ваш серверный код может использовать эту информацию для того, чтобы понять, как нужно ответить на предварительный запрос.
Ограничение на определенные методы запроса
Аналогично тому, как мы указываем заголовки запроса, так и конечные точки сервера могут определять, какие HTTP-методы из различных источников будут разрешены. Установите заголовок Access-Control-Allow-Methods. Его значение – список названий методов через запятую.
Access-Control-Allow-Methods: GET, POST, DELETE
Браузер отправляет заголовок Access-Control-Request-Method с предварительным запросом. Таким образом сервер узнает HTTP-метод, который будет использоваться для выполнения окончательного запроса.
Cookie-файлы и учетные данные
CORS-запросы, как правило, не отправляют cookie-файлы, так как в них может содержаться конфиденциальные учетные данные, которые идентифицируют отправителя. Если вам необходимо добавить cookie-файл к запросу на другой источник, то это нужно явно разрешить в клиентском коде:
fetch("https://localhost/demo", {
mode: "cors",
credentials: "include"
});
К тому же сервер должен установить заголовок ответа Access-Control-Allow-Credentials: true, чтобы сообщить о том, что он соглашается на обмен cookie-файлами, которые содержат учетные данные.
Если вы используете заголовок Access-Control-Allow-Credentials, то нельзя использовать подстановочный символ (*) в заголовке Access-Control-Allow-Origin. Сервер должен явно указать источник для того, чтобы обезопасить конфиденциальность пользователя. Если вы будете использовать подстановочный символ, то браузер не выполнит запрос и вернет ошибку.
Предварительное кэширование
Предварительные запросы OPTIONS усиливают нагрузку на каждый запрос, который вы отправляете. При хорошем соединении задержка должна быть почти незаметной, и все же нерационально вызывать одну и ту же конечную точку раз за разом.
Вы можете указать браузеру кэшировать ответы на предварительные запросы. Для этого вам нужно установить заголовок Access-Control-Max-Age. Значение этого заголовка – это время, выраженное в секундах. В течение этого времени браузер может хранить кэшированный ответ. Последующие запросы к той же конечной точке в течении заданного периода времени не будут сопровождаться предварительными запросами.
Заключение
При первом знакомстве с технологией CORS она может сбивать с толку. Эта технология браузера, которая контролируется ответами сервера. Использование CORS неизбежно, но при этом оно может оказаться неуправляемым, если у вас нет доступа к серверному коду, с которым вы взаимодействуете.
Фактическая реализация CORS довольно проста. Убедитесь, что ваш API или CDN отправляет корректные заголовки ответов, в особенности это касается заголовка Access-Control-Allow-Origin. Если с этим проблем нет, то у вас будет безопасная связь между источниками, которая поможет избежать вмешательства злоумышленников.