Cross origin html
XMLHttpRequest: кросс-доменные запросы
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/fetch-crossorigin.
Обычно запрос XMLHttpRequest может делать запрос только в рамках текущего сайта. При попытке использовать другой домен/порт/протокол – браузер выдаёт ошибку.
Существует современный стандарт XMLHttpRequest, он ещё в состоянии черновика, но предусматривает кросс-доменные запросы и многое другое.
Большинство возможностей этого стандарта уже поддерживаются всеми браузерами, но увы, не в IE9-.
Впрочем, частично кросс-доменные запросы поддерживаются, начиная с IE8, только вместо XMLHttpRequest нужно использовать объект XDomainRequest.
Кросс-доменные запросы
Разберём кросс-доменные запросы на примере кода:
- Мы создаём XMLHttpRequest и проверяем, поддерживает ли он событие onload . Если нет, то это старый XMLHttpRequest , значит это IE8,9, и используем XDomainRequest .
- Запрос на другой домен отсылается просто указанием соответствующего URL в open . Он обязательно должен быть асинхронным, в остальном – никаких особенностей.
Контроль безопасности
Кросс-доменные запросы проходят специальный контроль безопасности, цель которого – не дать злым хакерам™ завоевать интернет.
Серьёзно. Разработчики стандарта предусмотрели все заслоны, чтобы «злой хакер» не смог, воспользовавшись новым стандартом, сделать что-то принципиально отличное от того, что и так мог раньше и, таким образом, «сломать» какой-нибудь сервер, работающий по-старому стандарту и не ожидающий ничего принципиально нового.
Давайте, на минуточку, вообразим, что появился стандарт, который даёт, без ограничений, возможность делать любой странице HTTP-запросы куда угодно, какие угодно.
Как сможет этим воспользоваться злой хакер?
Он сделает свой сайт, например http://evilhacker.com и заманит туда посетителя (а может посетитель попадёт на «злонамеренную» страницу и по ошибке – не так важно).
Когда посетитель зайдёт на http://evilhacker.com , он автоматически запустит JS-скрипт на странице. Этот скрипт сделает HTTP-запрос на почтовый сервер, к примеру, http://gmail.com . А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками.
Поэтому хакер сможет написать на http://evilhacker.com код, который, сделав GET-запрос на http://gmail.com , получит информацию из почтового ящика посетителя. Проанализирует её, сделает ещё пачку POST-запросов для отправки писем от имени посетителя. Затем настанет очередь онлайн-банка и так далее.
Спецификация CORS налагает специальные ограничения на запросы, которые призваны не допустить подобного апокалипсиса.
Запросы в ней делятся на два вида.
Простыми считаются запросы, если они удовлетворяют следующим двум условиям:
- Простой метод: GET, POST или HEAD
- Простые заголовки – только из списка:
- Accept
- Accept-Language
- Content-Language
- Content-Type со значением application/x-www-form-urlencoded , multipart/form-data или text/plain .
«Непростыми» считаются все остальные, например, запрос с методом PUT или с заголовком Authorization не подходит под ограничения выше.
Принципиальная разница между ними заключается в том, что «простой» запрос можно сформировать и отправить на сервер и без XMLHttpRequest, например при помощи HTML-формы.
То есть, злой хакер на странице http://evilhacker.com и до появления CORS мог отправить произвольный GET-запрос куда угодно. Например, если создать и добавить в документ элемент
Комментарии
- Если вам кажется, что в статье что-то не так — вместо комментария напишите на GitHub.
- Для одной строки кода используйте тег , для нескольких строк кода — тег
, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
Что такое CORS
Многие из нас встречались с подобной ошибкой:
Access to XMLHttpRequest at ‘XXXX’ from origin ‘YYYY’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource..
Эта статья рассказывает что означает эта ошибка и как от нее избавиться.
Создадим тестовый сайт на Node.js с открытым API и запустим его по адресу http://127.0.0.1:3000.
Пусть там будет примерно такая функция получения GET запроса:
Пусть там будет простая функция входа в систему, где пользователи вводят общее секретное слово secret и им затем ему устанавливается cookie, идентифицируя их как аутентифицированных:
И пусть у нас будет некое приватное API для каких нибудь личных данных в /private, только для аутентифицированных пользователей.
Запрос нашего API через AJAX из других доменов
И допустим у нас есть какое-нибудь клиентское приложение работающее с нашим API. Но учтем что, наше API находится по адресу http://127.0.0.1:3000/public, а наш клиент размещен на http://127.0.0.1:8000, и на клиенте есть следующий код:
И это не будет работать!
Если мы посмотрим на вкладку network в консоле Хрома при обращение c http://127.0.0.1:8000 к http://127.0.0.1:3000 то там не будет ошибок:
Сам по себе запрос был успешным, но результат оказался не доступен. Описание причины можно найти в консоли JavaScript:
Ага! Нам не хватает заголовка Access-Control-Allow-Origin. Но зачем он нам и для чего он вообще нужен?
Same-Origin Policy
Причиной, по которой мы не получим ответ в JavaScript, является Same-Origin Policy. Эта ограничительная мера была придумана разработчиками браузеров что бы веб-сайт не мог получить ответ на сгенерированный AJAX запрос к другому веб-сайту находящемуся по другому адресу .
Например: если вы заходите на sample.org, вы бы не хотели, чтобы этот веб-сайт отправлял запрос к примеру на ваш банковский веб-сайт и получал баланс вашего счета и транзакции.
Same-Origin Policy предотвращает именно это.
«источник (origin)» в этом случае состоит из
- протокол (например http )
- хост (например example.com )
- порт (например 8000 )
Так что http://sample.org и http://www.sample.org и http://sample.org:3000 – это три разных источника.
Пару слов о CSRF
Обратите внимание, что существует класс атак, называемый подделкой межсайтовых запросов (Cross Site Request Forgery – csrf ), от которых не защищает Same-Origin Policy.
При CSRF-атаке злоумышленник отправляет запрос сторонней странице в фоновом режиме, например, отправляя POST запрос на веб-сайт вашего банка. Если у вас в этот момент есть действительный сеанс с вашим банком, любой веб-сайт может сгенерировать запрос в фоновом режиме, который будет выполнен, если ваш банк не использует контрмеры против CSRF.
Так же обратите внимание, что, несмотря на то, что действует Same-Origin Policy, наш пример запроса с сайта secondparty.com на сайте 127.0.0.1:3000 будет успешно выполнен – мы просто не соможем получить доступ к результатам. Но для CSRF нам не нужен результат …
Например, API, которое позволяет отправлять электронные письма, выполняя POST запрос, отправит электронное письмо, если мы предоставим ему правильные данные. Злоумышленнику не нужно заботится о результате, его забота это отправляемое электронное письмо, которое он получит независимо от возможности видеть ответ от API.
Включение CORS для нашего публичного API
Допустим нам нужно разрешить работу JavaScript на сторонних сайтах (например, 127.0.0.1:8000) что бы получать доступ к нашим ответам API. Для этого нам нужно включить CORS в заголовок ответа от сервера. Это делается на стороне сервера:
Здесь мы устанавливаем заголовку Access-Control-Allow-Origin значение *, что означает: что любому хосту разрешен доступ к этому URL и ответу в браузере:
Непростые запросы и предварительные запросы (preflights)
Предыдущий пример был так называемым простым запросом. Простые запросы – это:
- Запросы: GET,POST
- Тип содержимого следующего:
- text/plain
- application/x-www-form-urlencoded
- multipart/form-data
Допустим теперь 127.0.0.1:8000 немного меняет реализацию, и теперь он обрабатывает запросы в формате JSON:
Но это снова все ломает!
На этот раз консоль показывает другую ошибку:
Любой заголовок, который не разрешен для простых запросов, требует предварительного запроса (preflight request).
Этот механизм позволяет веб-серверам решать, хотят ли они разрешить фактический запрос. Браузер устанавливает заголовки Access-Control-Request-Headers и Access-Control-Request-Method, чтобы сообщить серверу, какой запрос ожидать, и сервер должен ответить соответствующими заголовками.
Но наш сервер еще не отвечает с этими заголовками, поэтому нам нужно добавить их:
Теперь мы снова может получить доступ к ответу.
Credentials и CORS
Теперь давайте предположим, что нам нужно залогинится на 127.0.0.1:3000 что бы получить доступ к /private с конфиденциальной информацией.
При всех наших настройках CORS может ли другой сайт так же получить эту конфиденциальную информацию?
Мы пропустили код реализации входа в на сервер так как он не обязателен для объяснения материала.
Независимо от того, попытаемся ли мы залогинится на 127.0.0.1:3000 или нет, мы увидим «Please login first».
Причина в том, что cookie от 127.0.0.1:3000 не будут отправляться, когда запрос поступает из другого источника. Мы можем попросить браузер отправить файлы cookie клиенту, даже если запрос с других доменов:
Но опять это не будет работать в браузере. И это хорошая новость, на самом деле.
Итак, мы не хотим, чтобы злоумышленник имел доступ к приватным данным, но что, если мы хотим, чтобы 127.0.0.1:8000 имел доступ к /private?
В этом случае нам нужно установить для заголовка Access-Control-Allow-Credentials значение true:
Но это все равно пока еще не сработает. Это опасная практика – разрешать любые аутентифицированные запросы с других источников.
Браузер не позволит нам так легко совершить ошибку.
Если мы хотим разрешить 127.0.0.1:8000 доступ к /private, нам нужно указать точный источник в заголовке:
Теперь http://127.0.0.1:8000 также имеет доступ к приватным данным, в то время как запрос с любого другого сайта будет заблокирован.
Разрешить множественные источники (origin)
Теперь мы разрешили одному источнику делать запросы к другому источнику с данными аутентификации. Но что, если у нас есть несколько других источников?
В этом случае мы, вероятно, хотим использовать белый список:
Опять же: не отправляйте напрямую req.headers.origin в качестве разрешенного заголовка CORS. Это позволит любому веб-сайту получить доступ к приватным данным.
Из этого правила могут быть исключения, но, по крайней мере, дважды подумайте, прежде чем внедрять CORS с учетными данными без белого списка.
Заключение
В этой статье мы рассмотрели Same-Origin Policy и то, как мы можем использовать CORS, чтобы разрешать запросы между источниками, когда это необходимо.
Это требует настройки на стороне сервера и на стороне клиента и в зависимости от запроса вызовет предварительный (preflight) запрос.
При работе с аутентифицированными запросами перекрестного происхождения следует проявлять дополнительную осторожность. Белый список может помочь разрешить нескольким источникам без риска утечки конфиденциальных данных (которые защищены аутентификацией).
Выводы
- Браузер использует Same-origin policy, чтобы не обрабатывать AJAX ответы от веб-сайтов расположенных на адресах отличных от адреса с которого была загружена веб страница.
- Same-origin policy не запрещает генерировать запросы к другим сайтам, но запрещает обрабатывать от них ответ.
- CORS (Cross-Origin Resource Sharing) механизм, который использует дополнительные заголовки HTTP, чтобы дать браузерам указание предоставить веб-приложению, работающему в одном источнике, доступ к ответу на запрос к ресурсам из другого источника.
- CORS вместе с credentials (с данными аутентификации) требует осторожности.
- CORS это браузерная политика. Другие приложения не затрагиваются этим понятием.
Понимание CORS
Правило одного источника является важным принципом обеспечения безопасности, реализованное web браузерами для предотвращения выполнения JavaScript кодом запросов к другим источникам(т.е. к другим доменам), а не к тому, с которого он передан. Несмотря на эффективность такого правила, оно также ограничивает в законных взаимодействиях между сервером и клиентами из надежных источников.
Cross-Origin Resource Sharing (CORS) является техникой для ослабления правила одного источника, позволяя JavaScript на web странице обрабатывать ответ от REST API от другого источника.
Обработка простых CORS запросов
В простейшем случае, междоменные взаимодействия начинаются с запроса GET, POST или HEAD к ресурсу на сервере. В данном случае, тип содержимого POST запроса ограничен application/x-www-form-urlencoded , multipart/form-data или text/plain . Запрос включает заголовок Origin , который указывает на происхождение клиентского кода.
Сервер будет учитывать Origin запроса и принимать или отказывать в обработке запроса. Если сервер принял запрос, то он ответит запрашиваемым ресурсом в заголовке Access-Control-Allow-Origin . Этот заголовок будет указывать клиенту с каким происхождением клиента будет разрешен доступ к ресурсу. Принимая во внимание, что Access-Control-Allow-Origin соответствует Origin запроса, браузер разрешит запрос.
С другой стороны, если Access-Control-Allow-Origin отсутствует в ответе или если его нет в Origin запроса, то браузер не разрешит запрос.
К примеру, предположим, что клиентский код расположен на foo.client.com и отправляет запрос на bar.server.com:
Заголовок Origin говорит серверу, что клиентский код создан в http://foo.client.com. Так он проверяет правила одного источника и определяет, что он может обработать запрос. Ответ может выглядеть примерно так:
Access-Control-Allow-Origin указывает на то, что «http://foo.client.com» разрешен доступ, но не другим источникам.
Дополнительно Access-Control-Allow-Origin может быть установлен в «*», указывая на доступность всем. Это считается небезопасной практикой, кроме тех особых случаев, где API полностью публично и предназначено для использования любым клиентом.
Предполетные запросы
Если запрос может оказать влияние на пользовательские данные, то простого запроса недостаточно. Вместо этого, предполентый CORS запрос отправляется в перед отправкой необходимого запроса, чтобы гарантировать безопасность отправки запроса. Предполетные запросы необходимы в тех случаях, когда любой HTTP метод, отличный от GET, POST, HEAD или если тип содержимого POST запроса отличен от application/x-www-form-urlencoded , multipart/form-data или text/plain . Также, если запрос содержит любые собственные заголовки, то необходим предполетный запрос.
К примеру, предположим, что клиент, расположенный на foo.client.com, выполняет DELETE запрос к ресурсу не bar.server.com. Предполетный запрос принимает вид OPTIONS запроса со следующими заголовками:
Предполетный запрос фактически спрашивает сервер о доступности DELETE запроса без фактической отправки самого DELETE запроса. Если сервер разрешает такой запрос, то он ответит предполетному запросу примерно так:
Ответ предполетному запросу указывает(в заголовке Access-Control-Allow-Methods ) на то, что клиенту доступен DELETE запрос к передаваемому ресурсу. Заголовок Access-Control-Max-Age указывает на то, что этот предполетный ответ действует 84600 секунд или 1 день, после которого должен быть выполнен новый предполетный запрос.
В то же время клиенту будет доступно отправка настоящего DELETE запроса к ресурсу.
Overview
Send and retrieve data from web page in the background
Ajax (Asynchronous JavaScript and XML) is a technique on the client-side used to create asynchronous Web applications. With Ajax, Web applications can send data to, and retrieve data from, a server asynchronously (in the background) through JavaScript without interfering with the display and behavior of the existing page. Data can be retrieved using the XMLHttpRequest object.
Ajax request Example
Here is an example of a simple Ajax request using the GET method, written in JavaScript.
To see the demo page in action click here
jQuery example
This example uses the popular JavaScript library jQuery, to do the same thing as the example above.
To see the demo page in action click here
Why can’t we use Ajax outside our domain?
If you try use ajax outside your domain, you will probably get this error message:
The reason you get this error message is because of the Same-origin policy. The policy permits scripts running on pages originating from the same site to access each other’s data with no specific restrictions, but prevents scripts access to data that is stored on a different domain.
This could be a problem if you are trying to access publicly hosted data, but there are ways round it.
Here is the list of methods:
- Implement CORS (Cross-Origin Resource Sharing)
- Use JSONP (JSON Padding)
- Use postMessage method
- Setting up a local proxy
Cross-Domain AJAX Request methods
CORS (Cross-Origin Resource Sharing)
CORS is a mechanism that allows resources on a web page to be requested from another domain outside the domain the resource originated from. In particular, JavaScript’s AJAX calls can use the XMLHttpRequest mechanism. Such «cross-domain» requests would otherwise be forbidden by web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross-origin request. It is more useful than only allowing same-origin requests, but it is more secure than simply allowing all such cross-origin requests.
CORS is a W3C recommendation and supported by all major browsers. It makes use of HTTP headers to help browser decide if a cross-domain AJAX request is secure. Basically, when you make a CORS request, browser adds Origin header with the current domain value.
The server, where the script makes its’ CORS request, checks if this domain is allowed and sends response with Access-Control-Allow-Origin response header. Upon receiving, browser checks if the header is present and has the current domain value. If domains match, browser carries on with AJAX request, if not throws an error.
To allow access from all domains, a server can send the following response header:
To make a CORS request you simply use XMLHttpRequest in Firefox 3.5+, Safari 4+ & Chrome and XDomainRequest object in IE8+. When using XMLHttpRequest object, if the browser sees that you are trying to make a cross-domain request it will seamlessly trigger CORS behaviour.
Here is a javascript function that helps you create a cross browser CORS object:
JSONP (JSON Padding)
JSONP or «JSON with padding» is a communication technique used in JavaScript programs running in web browsers to request data from a server in a different domain, something prohibited by typical web browsers because of the same-origin policy. JSONP takes advantage of the fact that browsers do not enforce the same-origin policy on
The browser will, in order, download the script file, evaluate its contents, interpret the raw JSON data as a block, and throw a syntax error. Even if the data were interpreted as a JavaScript object literal, it could not be accessed by JavaScript running in the browser, since without a variable assignment object literals are inaccessible.
In the JSONP usage pattern, the URL request pointed to by the
What is CORS ( Cross-Origin Resource Sharing ) ?
Before talking about CORS, let’s start with cross-origin access. A web document makes a cross-origin access when it requests a resource from a different domain, protocol, or port.
For example, a web document served from http://domain-a.com makes an image request for http://domain-b.com .
For security reason, browsers restrict the way of cross-origin access according to the same-origin policy.
Same-origin Policy
The same-origin policy controls interactions between two different origins, and these interactions can be placed in three categories :
- Cross-origin write is allowed : links, redirects and form submissions.
- Cross-origin embed is allowed : JavaScript tag , CSS link
- , images , media files and , plug-ins , and
- Cross-origin read is not allowed : XMLHttpRequest, Fetch API
When developing front-end code for requesting data from different domain with XMLHttpRequest or Fetch API, you might encounter the problem of cross-origin, as the following screenshot. The error shows browser doesn’t allow cross-origin access because it’s a cross-origin read.
One of the solution to allow cross-origin access is using CORS.
CORS ( Cross-Origin Resource Sharing )
The CORS mechanism gives web servers cross-domain access controls, which enable secure cross-domain data transfers.
How to make the CORS work ?
To make the CORS work, we can add HTTP headers that allow servers to describe a set of origin that are permitted to read that information using a web browser, and for different types of request, we have to add different headers. The request can be mainly divided into the simple request and the preflighted request.
Simple Requests
A request wouldn’t trigger a CORS preflight is so-called Simple Request. A simple request should meet all the following conditions :
For a simple request, to make the CORS work, web server should set a HTTP header :
Setting this header means that the resource can be accessed by any domain. If we want to restrict to one specific domain, we can set it as :
Preflighted Requests
A preflighted request first send an HTTP request by the OPTIONS method to the server to determine whether the actual request ( the following request ) is safe to send. A preflighted request should meet all the following conditions :
For a preflighted request, to make the CORS work, web sever should set some HTTP headers :
In addition to Access-Control-Allow-Origin , we should also set Access-Control-Allow-Methods to specify the method can be used for the the actual request, and Access-Control-Allow-Headers to confirm these headers are permitted to be used with the actual request, and finally Access-Control-Max-Age to give the value in seconds for how long the response to the preflight request can be cached.
Based on the request, we set different response headers on web server to make CORS work. However, can we do cross-domain without CORS ?
Without CORS ?
As mentioned above, the reason we can’t do cross-domain access is the same-origin policy, but some interactions are typically allowed, like