У меня есть HTML-страница, предназначенная для встраивания в другой веб-сайт.
С помощью директивы CSP Frame-Ancestors я могу ограничить, на каких страницах разрешено использовать iframe. Это не позволяет использовать iframe на неправильном сайте, что и является целью этой политики безопасности.
В моем сценарии я также хочу заблокировать загрузку этой страницы отдельно, потому что это не имеет смысла и сбивает с толку людей, пытающихся с ней взаимодействовать. Его следует использовать только в контексте iframe.
Насколько я могу судить, у CSP нет возможности заблокировать автономный запуск страницы. Он работает как белый список, поэтому я могу только разрешить больше iframe и ничего не запрещать.
До сих пор я нашел только решения на стороне клиента (например, это, которое использует JavaScript), чтобы обнаружить это в браузере и затем что-то сделать. Хотя это жизнеспособное решение, я предпочитаю уже заблокировать его на стороне сервера.
Я просмотрел заголовки запросов и обнаружил, что только при первом запросе «Referer:» кажется «пригодным для использования», но ненадежным.
Итак, мой вопрос: можно ли обнаружить/заблокировать эту серверную часть или есть ли способ заставить браузер заблокировать это (аналогично CSP)? Или маршрут JavaScript действительно единственный способ справиться с этим?
Да, у меня есть. Проблема в том, что если внутри iframe происходит взаимодействие (скажем, форма, которая загружает новую страницу), то эта дополнительная вещь теряется на следующей. Хотя это и возможно, но из-за этого немного сложнее обеспечить его надежную работу.
Если
window.parent === window
тогда ваша страница не внутри iframe
. Тем не менее, это надежно только в том случае, если у вас одинаковое происхождение. Поскольку ваш сценарий полностью посвящен случаям, когда происхождение не то же самое, вам понадобится более аккуратное решение. Способ решения этой проблемы — убедиться, что ваша страница будет отображаться только в том случае, если она получит сообщение.
Шаги для реализации этого:
Убедитесь, что на вашей странице есть правильный head
, теги link
, script
и meta
и пустой body
.
Про postMessage
читайте здесь. Пример с этой страницы:
window.addEventListener(
"message",
(event) => {
if (event.origin !== "http://example.org:8080") return;
// …
},
false,
);
Убедитесь, что внутри data
вы передаете что-то, что проверяет отображаемую страницу, например токен, понятный вашему серверу.
Итак, вы получили data
, давайте убедимся, что вы отправили его на сервер и, если оно действительно, получите содержимое, которое вы хотели отобразить, и добавите его в body
как innerHTML
. Упрощенным решением было бы создать на вашем сервере одноразовый буквенно-цифровой токен (возможно, случайный текст), который связан с хостом страницы, на которую должен быть встроен ваш iframe, и как только ваш сервер получит этот запрос, проверьте, является ли токен существует и связан с конкретным хостом, который отправил вам хост, ответьте на запрос действительным содержимым (если токен действителен) или сообщением об ошибке (если токен недействителен) и, конечно же, удалите токен после проверки. , но прежде чем ответить.
Реализуйте сценарий Javascript, который
postMessage
и передайте действительные данные, включая токен)Спасибо за предложения по тому, как сделать это с помощью JavaScript. Мой основной вопрос: возможно ли сделать серверную часть; т.е. без какого-либо кода на стороне клиента (например, JavaScript).
@NielsBasjes Нет, на стороне сервера это невозможно.
@NielsBasjes нет, запрос, отправляемый из iframe, представляет собой запрос GET для отправки на сервер, и не будет различий между отправкой из iframe или за пределами всех iframe.
Пожалуйста, проверьте мой собственный ответ на этот вопрос; Я нашел новую функцию браузера, которая фактически позволяет различать серверную часть внутри и снаружи iframe таким образом, который достаточно хорош для моего варианта использования. Вы можете использовать отдельные файлы cookie внутри и снаружи iframe.
Пользователи @NielsBasjes могут скопировать исходный код iframe в память и вставить его в браузер или окно чата для кого-то другого. Пожалуйста, подумайте об этом сценарии и о том, подходит ли он для вашего варианта использования. Если вы по-прежнему предпочитаете описанный вами подход, возможно, вы захотите, чтобы запросы на публикацию принимались только после установления сеанса. Это предотвратит загрузку ее в качестве главной страницы, но новая проблема будет заключаться в том, что в следующий раз, когда вы вставите ее в iframe, у вас будет другой iframe, поэтому вам нужно будет каким-то образом уничтожить и воссоздать сеанс.
И именно так я и хочу: тот же iframe на отдельном сайте — это отдельная сессия. Ограничение всего этого запросами POST в некоторых случаях также может быть вариантом дополнения к этому. Спасибо.
@NielsBasjes рад помочь. Дайте мне знать, намерены ли вы принять свой ответ или мою отредактированную версию.
Я нашел способ сделать это, который работает только с современными браузерами (достаточно хорош для меня) и работает в моем конкретном контексте.
Почему и как это работает в моем контексте:
Проблема в том, что если ПОСЛЕ того, как вы это сделали (т. е. существует файл cookie с активным сеансом), а затем откройте URL-адрес iframed в отдельном окне вне ситуации iframed; это будет работать (чего я не хочу).
Пробуя что-то, я заметил сообщение в консоли браузера
Third-party cookie will be blocked in future Chrome versions as part of Privacy Sandbox.
.Third-party cookie will be blocked in future Microsoft Edge versions as part of unpartitioned third-party cookie deprecation.
Итак, я немного покопался в этом разделении, и оказалось, что если файл cookie разделен, то у него будет отдельная область действия внутри и снаружи iframe.
Таким образом, если страница в iframe получила параметры, которые делают ее допустимой, то та же страница, открытая вне iframe, будет в другом сеансе! Другой сеанс, в котором отсутствуют необходимые атрибуты, поэтому я могу четко показать ошибку на стороне сервера.
Опытный пользователь может легко обойти это, изменив свои файлы cookie. Для моего варианта использования этого достаточно, потому что это делает невозможным случайное событие.
После некоторых дальнейших поисков я обнаружил, что включить это довольно просто (если вы знаете, как это сделать):
На основании этого https://github.com/spring-projects/spring-framework/issues/31454#issuecomment-1905442337 Теперь у меня это есть в моем (котлин) коде, и у меня есть то, что я хотел.
@Bean
fun forceAllCookiesToBePartitioned(): TomcatContextCustomizer {
val cookieProcessor = Rfc6265CookieProcessor()
cookieProcessor.partitioned = true
return TomcatContextCustomizer { context: Context ->
context.usePartitioned = true
context.cookieProcessor = cookieProcessor
}
}
Самый простой вариант — поискать заголовок и значение запроса:
Я протестировал это на последних версиях Chrome, Edge, Firefox и Safari, которые охватывают всю мою группу пользователей.
Я могу легко проверить наличие этого значения и предоставить страницу с ошибкой, если оно отсутствует.
Рассматривали ли вы возможность добавления параметра запроса или специального заголовка с настраиваемым значением на страницу во фрейме? Если его нет, сервер может заблокировать запрос.