Знайте разницу между простым HTTP-запросом и запросом XHR.
Веб-приложение Origin — https://github.com
Когда вы открываете веб-сайт, вводя Origin Web App, ваш браузер отправляет простые HTTP-запросы. CORS здесь не применим. Поскольку мы добровольно просим предоставить контент из Origin Web App. Ваш браузер будет/должен обслуживать его без каких-либо проблем. Но когда дело доходит до запросов XHR, JavaScript, который загружается Origin Web App в вашем браузере, может отправить запрос на любой сервер (https://netflix.com), запрашивающий ресурс. Теперь Origin Web App принадлежит Github, но https://netflix.com принадлежит Netflix, и этот сервер потенциально может обслуживать что угодно. Github не может взять под контроль сервер, принадлежащий Netflix. Это имеет множество последствий для безопасности, поскольку может привести к краже контента с одного веб-сайта (это может быть финансовый веб-сайт) на любой удаленный сервер.
К счастью, CORS очень хорошо решает эту проблему с помощью заданного набора правил.
Это стандарт, определенный W3C для включения запросов между источниками между клиентом (браузером) и сервером для совместного использования ресурсов, в то же время поддерживая безопасность. Любой браузер будет соответствовать этим стандартам, чтобы предотвратить загрузку ресурсов со сторонних серверов.
Заголовок указывает, откуда исходит запрос. Он отправляется с запросами CORS, а также с запросами POST.
Синтаксис:
Origin: <scheme> "://" <hostname> [":" <port> ]
Примеры:
Origin: https://netflix.com
Origin: http://netflix.com:443
Origin: http://localhost:1443
Этот заголовок используется браузером при отправке предполетных запросов, чтобы указать, какой метод запроса будет использоваться при выполнении фактического запроса.
Синтаксис:
Access-Control-Request-Method: <method>
<method>
может быть GET
, POST
или DELETE
.
Пример:
Access-Control-Request-Method: POST
Этот заголовок снова используется браузером в предполетных запросах, чтобы указать, какие заголовки запроса должны использоваться при выполнении фактического запроса. Чтобы использовать несколько заголовков, их необходимо разделить запятой.
Синтаксис:
Access-Control-Request-Headers: <header-name>
Access-Control-Request-Headers: <header-name>, <header-name>
Пример:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Важно: весь заголовок должен быть внесен в безопасный список CORS или иметь собственный заголовок, например X-Custom-Header
.
Accept
Accept-Language
Content-Language
Content-Type
Допустимые значения: application/x-www-form-urlencoded
, multipart/form-data
и text/plain
.
Следующие заголовки возвращаются в предполетных запросах.
Этот заголовок указывает, разрешен ли запрошенный источник. Ваш браузер выберет успешный или неудачный запрос, сопоставив запрошенный источник с этим.
Синтаксис:
Access-Control-Allow-Origin: *
Для запросов без учетных данных значение *
можно указать в качестве подстановочного знака. Это означает, что ваш браузер разрешает запросы из любого источника.
Access-Control-Allow-Origin: <origin>
Когда вы получаете только один источник в заголовке ответа, это означает, что ваш сервер/веб-приложение на основе запрошенного Origin
отвечает тем же Origin
если это разрешено. Ваш сервер также должен ответить Vary, чтобы указать, что он меняется в зависимости от заголовка запроса.
Пример:
Access-Control-Allow-Origin: https://github.com
Ваш браузер выполнит фактический запрос, если одно из значений в этом заголовке соответствует. Когда возвращается подстановочный знак *
, это означает, что разрешен любой метод.
Синтаксис:
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
Пример:
Access-Control-Allow-Methods: GET, POST
Ваш браузер выполнит фактический запрос, если все запрошенные заголовки разрешены.
Синтаксис:
Access-Control-Allow-Headers: <header-name>, <header-name>
Access-Control-Allow-Headers: *
Пример:
Access-Control-Allow-Headers: Accept, Content-Type
Подстановочный знак *
сообщает браузеру, что разрешен любой заголовок.
Сообщает, как долго могут храниться в кэше результаты предполетной подготовки.
Access-Control-Max-Age: <delta-seconds>
максимум нет. секунд результаты могут быть кэшированы.
У каждого браузера есть максимальный предел,
Изменяйте заголовок, обычно используемый для целей кэширования, чтобы определить, можно ли использовать кэшированный ответ или его необходимо повторно кэшировать на основе значения заголовка.
Допустим, в CORS сервер разрешает несколько источников на основе запрошенного Origin
он вернет конкретный URL-адрес в Access-Control-Allow-Origin
.
Синтаксис:
Vary: <header-name>
Пример:
Допустим, github.com позволяет как https://github.com, так и https://netflix.com запрашивать ресурсы. Рассмотрим следующие сценарии:
Сценарий 1:
ОПЦИИ завитка -X https://github.com/api/v1/gists/1
Origin: https://github.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://github.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
Теперь в этом сценарии браузер будет кэшировать результаты предполетного запроса в течение 10 минут.
Сценарий 2:
ОПЦИИ завитка -X https://github.com/api/v1/gists/1
Origin: https://netflix.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://netflix.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
Теперь вы могли заметить, что Access-Control-Allow-Origin
содержит https://netflix.com. Это пример того, как ответ варьируется в зависимости от заданного Origin
. То же самое касается и максимального возраста этого ответа, который кэшируется всего на 5 минут, в отличие от первого сценария.
Некоторые запросы не запускают предполетный запрос CORS. Мы называем это простыми запросами . Оно должно соответствовать следующим условиям:
Один из разрешенных методов:
Помимо заголовков, которые автоматически создаются пользовательским агентом (например, Connection
или User-Agent
), единственными заголовками, которые разрешено устанавливать вручную, являются заголовки запросов, внесенные в безопасный список CORS, и следующие:
DPR
Downlink
Save-Data
Viewport-Width
Width
Ни для одного объекта XMLHttpRequestUpload
, используемого в запросе, не зарегистрированы прослушиватели событий.
В запросе не используется объект ReadableStream
.
Пример:
Запрос:
GET /api/v1/public-data/ HTTP/1.1
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Заголовки ответов:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
Когда запрос возвращает *
в заголовке Access-Control-Allow-Origin
. Это означает, что запрос можно сделать с любого Host
. В этом случае ваш браузер не будет выполнять предполетный запрос.
Ваш браузер выполнит предполетный запрос, чтобы определить, безопасно ли отправлять фактический запрос.
Пример:
const xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , 'https://bar.com/api/v1/post-here/' ) ;
xhr . setRequestHeader ( 'X-PINGOTHER' , 'pingpong' ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json;charset=UTF-8' ) ;
xhr . onreadystatechange = handler ;
xhr . send ( JSON . stringify ( { "email" : "[email protected]" } ) ) ;
Предполетный запрос:
OPTIONS /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept
Фактический запрос:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Если установлен нестандартный заголовок, например X-PINGOTHER
, ваш браузер не будет знать, безопасно ли выполнять запрос. Чтобы убедиться в безопасности, ваш браузер отправляет запрос OPTIONS с Access-Control-Request-Headers
содержащими X-PINGOTHER
и Content-Type
. После проверки заголовков ответа предполетного запроса ваш браузер отправляет фактический запрос.
Как правило, когда вы делаете запрос XHR, файлы cookie не передаются вместе с запросом. Если необходимо передать файлы cookie, вам придется установить флаг для объекта XMLHttpRequest
.
const xhr = new XMLHttpRequest ( ) ;
const url = 'http://bar.com/api/v1/credentialed-content/' ;
function callOtherDomain ( ) {
if ( invocation ) {
xhr . open ( 'GET' , url , true ) ;
xhr . withCredentials = true ;
xhr . onreadystatechange = handler ;
xhr . send ( ) ;
}
}
Запрос:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
Cookie: _session=NyV14oKXiS6HHopaf9nT
Когда выполняется запрос XMLHttpRequest
и установлен флаг withCredentials
, ваш браузер передает Cookie
в заголовке запроса.
Ответ:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: _session=AjBSqxj4T7bSySNTWeEm; expires=Wed, 31-05-2020 00:00:00 GMT
Когда ваш браузер замечает, Access-Control-Allow-Credentials
установлено значение true. Он будет учитывать заголовок Set-Cookie
и установит файл cookie.
Важно: подстановочный знак «*» не должен быть установлен в Access-Control-Allow-Origin
как указано в разделе «Запросы с учетными данными и подстановочные знаки».
Когда запрос учетных данных выполняется путем установки флага withCredentials
, сервер должен установить Access-Control-Expose-Headers
чтобы браузер знал, к каким заголовкам можно получить доступ.
В мире pre-cors заголовки ответов по умолчанию недоступны браузеру в запросе CORS. Таким образом, это сделано явным образом, чтобы браузер искал этот заголовок, чтобы прочитать открытые заголовки. Таким образом, спецификация CORS гарантирует, что старые браузеры не сломаются.
Это возвращается в предполетных запросах. Когда ваш браузер увидит это, он сможет получить доступ к заголовку Set-Cookie
. Как мы упоминали выше, в обычных запросах XHR ваш браузер не передает Cookie
в заголовке запроса, а также не читает заголовок ответа Set-Cookie
.
Синтаксис:
Access-Control-Allow-Credentials: true
Пример можно найти в разделе «Запрос с учетными данными».
Когда мы говорим «запрос с учетными данными», это означает, что файлы cookie передаются в запросе XHR или устанавливаются через заголовок ответа Set-Cookie
. Когда запрос XHR выполняется с флагом withCredentials, вы надеетесь получить ответ вместе с файлами cookie, которые необходимо установить. Но вы не можете ожидать Access-Control-Allow-Origin
будет «*», потому что это будет означать, что любой веб-сайт может использовать эти файлы cookie. По этой причине ваш браузер не сможет выполнить запрос, если увидит «*» в заголовке ответа Access-Control-Allow-Origin
.
Когда на предполетный запрос возвращается код 301/302, некоторые браузеры в настоящее время могут не поддерживать это. Вы можете получить такие ошибки, как:
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
Примечание. Чтобы найти обходные пути, проверьте Предварительные запросы и перенаправления документов Mozilla.
В браузере есть настройки, позволяющие отклонять все third-party
файлы cookie, если пользователь разрешает это. Например, если запрос сделан с https://foo.com
и сервер находится по адресу https://bar.com
, ваш браузер не будет устанавливать файлы cookie, отправленные https://bar.com
.