CORS в AMP
Многие компоненты и расширения AMP используют удаленные конечные точки с помощью запросов Cross-Origin Resource Sharing («совместное использование ресурсов между разными источниками», CORS). В этом документе описываются ключевые аспекты применения CORS в AMP. Чтобы подробнее узнать о самом механизме CORS, обратитесь к спецификации CORS на сайте W3.
- Зачем применять CORS в пределах моего собственного источника?
- Использование файлов cookie в CORS-запросах
-
-
- 1) Разрешайте запросы с определенных источников CORS
- 2) Разрешайте запросы в пределах одного источника
-
Зачем применять CORS в пределах моего собственного источника?
Вам может быть непонятно, зачем использовать CORS в запросах к вашему собственному источнику. Давайте рассмотрим это подробнее.
Компоненты AMP, которые получают динамические данные (например, amp-form, amp-list и т. п.), отправляют запросы CORS к удаленным конечным точкам для получения данных. Если ваша AMP-страница содержит такие компоненты, вам необходимо предусмотреть обработку CORS, чтобы эти запросы не завершались ошибкой.
Проиллюстрируем это на примере.
Допустим, у вас есть AMP-страница, на которой перечислены продукты с ценами. Чтобы обновить цены на странице, пользователь нажимает кнопку, которая извлекает актуальные цены из конечной точки JSON (с помощью компонента amp-list). JSON-данные находятся в вашем домене.
Ну вот, страница находится в моем домене, а JSON — тоже в моем домене. Не вижу проблем!
Да, но как пользователь попал на вашу AMP-страницу? Что, если он имеет дело с кешированной страницей? Вполне вероятно, что пользователь не открыл вашу AMP-страницу напрямую, а нашел ее через другую платформу. Например, Поиск Google использует Google AMP Cache для быстрого рендеринга AMP-страниц; кешированные страницы, которые выдаются из Google AMP Cache, загружаются со стороннего домена. Когда пользователь нажимает кнопку, чтобы обновить цены на вашей странице, кешированная AMP-страница отправляет запрос на ваш исходный домен для получения цен, что является несоответствием между источниками (кеш -> исходный домен). Чтобы сделать возможными подобные запросы, вам необходимо предусмотреть обработку CORS; в противном случае запрос не будет выполнен.
Хорошо, что мне делать?
- Для AMP-страниц, загружающих динамические данные, — убедитесь, что вы протестировали кешированную версию страницы; не ограничивайтесь тестированием на своем собственном домене (см. раздел Тестирование CORS в AMP ниже).
- Следуйте приведенным в данном документе инструкциям по обработке CORS-запросов и CORS-ответов.
Использование файлов cookie в CORS-запросах
Большинство компонентов AMP, которые используют CORS-запросы, либо автоматически устанавливают режим учетных данных, либо позволяют автору при желании включить его. Например, компонент amp-list
получает динамическое содержимое из конечной точки CORS JSON и позволяет автору устанавливать режим учетных данных с помощью атрибута credentials
.
Пример: включение персонализированного контента в amp-list с помощью файлов cookie
<amp-list credentials="include" src="<%host%>/json/product.json?clientId=CLIENT_ID(myCookieId)" > <template type="amp-mustache"> Your personal offer: ${{price}} </template> </amp-list>
Указывая режим учетных данных, источник может включать cookie в CORS-запрос и также устанавливать cookie в ответе (с учетом ограничений для сторонних файлов cookie).
Ограничения для сторонних файлов cookie
Ограничения сторонних файлов cookie, указанные в браузере, также применяются к CORS-запросам с учетными данными в AMP. Эти ограничения зависят от браузера и платформы, но в некоторых браузерах источник может устанавливать файлы cookie только в том случае, если пользователь ранее посещал источник в основном (верхнем) окне. Или, другими словами, только после того, как пользователь напрямую посетил сам исходный сайт. Исходя из этого, сервис, доступ к которому осуществляется через CORS, не может рассчитывать на возможность устанавливать файлы cookie по умолчанию.
Безопасность CORS в AMP
Чтобы обеспечить корректность и безопасность запросов с ваших AMP-страниц и ответов на них, вы должны:
Если вы используете в своем бэкенде Node, вы можете использовать промежуточное ПО AMP CORS, которое является частью AMP Toolbox.
Проверка CORS-запросов
Когда ваша конечная точка получает CORS-запрос:
- Убедитесь, что заголовок CORS-запроса
Origin
указывает на разрешенный источник (домен издателя + AMP-кеши). - Если заголовок Origin отсутствует, убедитесь, что источник запроса не отличается от домена его получения (с помощью
AMP-Same-Origin
).
1) Разрешайте запросы с определенных источников CORS
Конечные точки CORS получают источник запроса посредством HTTP-заголовка Origin
. Конечные точки должны разрешать запросы только от: (1) собственного источника издателя; и (2) всех источников cacheDomain
, указанных в файле https://cdn.ampproject.org/caches.json.
Например, конечные точки должны разрешать запросы от:
- Поддомена Google AMP Cache:
https://<домен издателя>.cdn.ampproject.org
(например,https://nytimes-com.cdn.ampproject.org
)
2) Разрешайте запросы в пределах одного источника
Если в запросах в рамках одного источника отсутствует заголовок Origin
, AMP устанавливает специальный заголовок:
AMP-Same-Origin: true
Этот специальный заголовок отправляется средой выполнения AMP, когда запрос XHR не выходит за рамки одного источника (то есть если он выполняется из некешированного документа). Запросы, в которых есть заголовок AMP-Same-Origin:true
, следует разрешать.
Отправка заголовков CORS-ответа
Отправляемый после проверки CORS-запроса HTTP-ответ должен содержать следующие заголовки:
Access-Control-Allow-Origin: <origin>
Этот заголовок является требованием спецификации CORS от W3, где origin
обозначает источник запроса, который был разрешен через заголовок CORS-запроса Origin
(например, "https://<publisher's subdomain>.cdn.ampproject.org"
).
Хотя спецификация CORS от W3 позволяет возвращать в ответе значение *
, для повышения безопасности следует выполнять следующее:
- Если заголовок
Origin
присутствует, проверьте корректность значения заголовкаOrigin
, после чего продублируйте его в ответе.
Обработка запросов, меняющих состояние
Перед обработкой запросов, которые могут изменить состояние вашей системы (например, когда пользователь подписывается на список рассылки или отписывается от него), проверьте следующее:
Если установлен заголовок Origin
:
- Остановитесь и верните ответ с ошибкой, если источник не соответствует одному из следующих значений:
<publisher's domain>.cdn.ampproject.org
- источник издателя («ваш источник»)
где *
обозначает совпадение с подстановочным знаком, а не сам символ звездочки (*).
- В противном случае обработайте запрос.
Если заголовок Origin
НЕ установлен:
- Убедитесь, что запрос содержит заголовок
AMP-Same-Origin: true
. Если запрос не содержит этого заголовка, остановитесь и верните ответ с ошибкой. - В противном случае обработайте запрос.
Примеры обработки CORS-запросов и CORS-ответов
Есть два сценария поступления CORS-запросов к вашей конечной точке:
Давайте рассмотрим эти сценарии на примере. В нашем примере мы управляем сайтом example.com
, на котором размещена AMP-страница под названием article-amp.html.
AMP-страница содержит список amp-list
для извлечения динамических данных из файла data.json
, который также размещен на example.com
. Мы хотим обрабатывать запросы к нашему файлу data.json
, поступающие с нашей AMP-страницы. Эти запросы могут поступать с AMP-страницы из того же источника (без кеширования) или с AMP-страницы из другого источника (из кеша).
Разрешенные источники
Основываясь на том, что мы знаем о CORS и AMP (из раздела Проверка CORS-запросов выше), в нашем примере мы разрешим запросы из следующих доменов:
example.com
--- домен издателяexample-com.cdn.ampproject.org
--- поддомен Google AMP Cache
Заголовки ответов на разрешенные запросы
Наш ответ на запросы из разрешенных источников будет содержать следующие заголовки:
Access-Control-Allow-Origin: <origin>
Вот дополнительные заголовки ответа, которые мы можем включить в наш CORS-ответ:
Access-Control-Allow-Credentials: true Content-Type: application/json Access-Control-Max-Age: <delta-seconds> Cache-Control: private, no-cache
Псевдологика для работы с CORS
Наша логика обработки CORS-запросов и CORS-ответов может быть упрощена до следующего псевдокода:
IF CORS header present IF origin IN allowed-origins allow request & send response ELSE deny request ELSE IF "AMP-Same-Origin: true" allow request & send response ELSE deny request
Пример кода CORS
Ниже представлен пример функции JavaScript, которую мы могли бы использовать для обработки CORS-запросов и CORS-ответов:
function assertCors(req, res, opt_validMethods, opt_exposeHeaders) { var unauthorized = 'Unauthorized Request'; var origin; var allowedOrigins = [ 'https://example.com', 'https://example-com.cdn.ampproject.org', 'https://cdn.ampproject.org', ]; var allowedSourceOrigin = 'https://example.com'; //publisher's origin // If same origin if (req.headers['amp-same-origin'] == 'true') { origin = sourceOrigin; // If allowed CORS origin & allowed source origin } else if ( allowedOrigins.indexOf(req.headers.origin) != -1 && sourceOrigin == allowedSourceOrigin ) { origin = req.headers.origin; } else { res.statusCode = 403; res.end(JSON.stringify({message: unauthorized})); throw unauthorized; } res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Allow-Origin', origin); }
Примечание. Образец рабочего кода можно увидеть в файле amp-cors.js.
Сценарий 1. GET-запрос с AMP-страницы из того же источника
В следующем сценарии страница article-amp.html
запрашивает файл data.json
; источники не отличаются.
Если мы изучим запрос, то обнаружим следующее:
Request URL: https://example.com/data.json Request Method: GET AMP-Same-Origin: true
Поскольку этот запрос исходит из того же источника, заголовок Origin
отсутствует, но присутствует специальный заголовок запроса AMP-Same-Origin: true
. Мы можем разрешить этот запрос, поскольку он получен из того же источника ( https://example.com
).
Заголовки нашего ответа будут такими:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://example.com
Сценарий 2: GET-запрос с кешированной AMP-страницы
В следующем сценарии страница article-amp.html
, кешированная в Google AMP Cache, запрашивает файл data.json
; источники различаются.
Изучив этот запрос, мы обнаружим следующее:
Request URL: https://example.com/data.json Request Method: GET Origin: https://example-com.cdn.ampproject.org
Поскольку этот запрос содержит заголовок Origin
, мы можем удостовериться, что он поступил из разрешенного источника, и, следовательно, разрешить его.
Заголовки нашего ответа будут такими:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://example-com.cdn.ampproject.org
Работа с кешированными шрифтами
Google AMP Cache кеширует документы AMP HTML, изображения и шрифты для оптимизации скорости AMP-страницы. Хотя это желаемый результат, нам также следует предпринимать меры по обеспечению безопасности кешированных ресурсов. Мы вносим изменения в механизм выдачи кешированных ресурсов из AMP-кеша (как правило, это шрифты), так чтобы он использовал полученное из источника значение Access-Control-Allow-Origin
.
Предыдущее поведение (до октября 2019 г.)
Когда AMP-страница загружала https://example.com/some/font.ttf
из атрибута @font-face src
, AMP-кеш кешировал файл шрифта и выдавал его, как показано ниже (заголовок Access-Control-Allow-Origin
имеет значение символа подстановки).
- URL
https://example-com.cdn.ampproject.org/r/s/example.com/some/font.tff
- Access-Control-Allow-Origin: *
Новое поведение (октябрь 2019 г. и позже)
Предыдущая реализация являлась пермиссивной, что могло приводить к непредвиденному использованию шрифтов со сторонних источников. Благодаря внесенным изменениям AMP-кеш теперь отвечает тем же значением Access-Control-Allow-Origin
, что и исходный сервер. Чтобы правильно загружать шрифты из кешированного документа AMP, вам нужно принимать AMP-кеш в качестве одного из допустимых источников в заголовке.
Пример реализации:
function assertFontCors(req, res, opt_validMethods, opt_exposeHeaders) { var unauthorized = 'Unauthorized Request'; var allowedOrigins = [ 'https://example.com', 'https://example-com.cdn.ampproject.org', ]; // If allowed CORS origin if (allowedOrigins.indexOf(req.headers.origin) != -1) { res.setHeader('Access-Control-Allow-Origin', req.headers.origin); } else { res.statusCode = 403; res.end(JSON.stringify({message: unauthorized})); throw unauthorized; } }
Например, если вы хотите загрузить /some/font.ttf в https://example.com/amp.html
, ответ исходного сервера должен содержать заголовок Access-Control-Allow-Origin, как показано ниже.
Access-Control-Allow-Origin
; AMP-кеш продублирует это значение — то есть будет отвечать с заголовком Access-Control-Allow-Origin: *
. Если вы уже используете этот параметр, ничего менять не нужно. Мы планируем внести это изменение примерно в середине октября 2019 года. Предлагаем всем издателям AMP-контента, хранящим шрифты на собственном сервере, проверить, не повлияло ли на них данное обновление.
План внедрения
- 2019-09-30: более точный контроль над списком доменов, к которым будет применяться это изменение. Данная сборка должна выйти в течение этой недели.
- 2019-10-07: возможность использования тестовых доменов для ручного тестирования.
- 2019-10-14: общее внедрение функциональности (возможны изменения в зависимости от результатов тестирования).
Следите за соответствующей задачей здесь.
Тестирование CORS в AMP
Когда вы тестируете свои AMP-страницы, не забывайте также тестировать их кешированные версии.
Проверяйте страницу через URL-адрес кеша
Чтобы обеспечить правильное отображение и работу кэшированной AMP-страницы:
- В браузере откройте URL-адрес, который будет использовать AMP-кеш для доступа к вашей AMP-странице. Формат URL-адреса кеша можно определить с помощью этого инструмента из курса AMP By Example.
Например:
- URL:
https://amp.dev/documentation/guides-and-tutorials/start/create/
- Формат URL AMP-кеша:
https://www-ampproject-org.cdn.ampproject.org/c/s/www.ampproject.org/docs/tutorials/create.html
- Откройте инструменты разработчика в браузере и убедитесь, что ошибок нет и все ресурсы загружены корректно.
Проверяйте заголовки ответа сервера
Вы можете использовать команду curl
, чтобы убедиться, что ваш сервер отправляет правильные заголовки HTTP-ответа. В команде curl
укажите URL запроса и все специальные заголовки, которые хотите добавить.
Синтаксис: curl <request-url> -H <custom-header> - I
Протестируйте запрос из того же источника
К запросам из того же источника система AMP добавляет специальный заголовок AMP-Same-Origin:true
.
Вот наша команда curl для тестирования запроса из https://ampbyexample.com
к файлу examples.json
(в том же домене):
curl 'https://amp.dev/static/samples/json/examples.json' -H 'AMP-Same-Origin: true' -I
Результаты выполнения команды содержат правильные заголовки ответа (примечание: ненужная информация убрана):
HTTP/2 200 access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token access-control-allow-credentials: true access-control-allow-origin: https://ampbyexample.com access-control-allow-methods: POST, GET, OPTIONS
Протестируйте запрос с кешированной AMP-страницы
В CORS-запросах с отличающегося домена (т. е. из кеша) заголовок origin
является частью запроса.
Вот наша команда curl для тестирования запроса с кешированной в Google AMP Cache AMP-страницы к файлу examples.json
:
curl 'https://amp.dev/static/samples/json/examples.json' -H 'origin: https://ampbyexample-com.cdn.ampproject.org' -I
Результаты выполнения команды содержат правильные заголовки ответа:
HTTP/2 200
access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token
access-control-allow-credentials: true
access-control-allow-origin: https://ampbyexample-com.cdn.ampproject.org
access-control-allow-methods: POST, GET, OPTIONS