HTML5数据库和localStorage可以跨子域共享吗?
我正在尝试使用 Safari 跨子域共享数据。我想使用 HTML5 数据库(特别是 localStorage 因为我的数据只是键值对)。
但是,似乎无法从 sub.example.com
访问存储到 example.com
的数据(反之亦然)。在这种情况下有什么办法可以共享单个数据库吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
2016 年更新
Zendesk 的这个 库 为我工作。
示例:
Hub
请注意用于匹配字符串末尾的
$
。上例中的正则表达式将匹配valid.example.com
等来源,但不匹配invalid.example.com.malicious.com
。客户端
检查https://stackoverflow.com/a/39788742/5064633
Update 2016
This library from Zendesk worked for me.
Sample:
Hub
Note the
$
for matching the end of the string. The regular expression in the above example will match origins such asvalid.example.com
, but notinvalid.example.com.malicious.com
.Client
Check https://stackoverflow.com/a/39788742/5064633
有一种简单的方法可以使用跨域任何东西,只需创建简单的页面,该页面将作为托管在您尝试访问的域上的代理 iframe 包含在内,将 PostMessage 发送到该 iframe 并在 iframe 中,您可以进行 LocalStorage 数据库操作。以下是使用 lcoalStorage 执行此操作的文章的链接 。这是向子域中的不同页面发送消息的演示检查源代码,它使用 iframe 和 PostMessage 。
编辑:新sysend.js 库版本(上面使用demo) 如果浏览器支持则使用 BroadcastChannel,但仍然需要 Iframe。最近的版本还简化了跨源消息的使用,您可以在存储库中使用 iframe 的 html(或者您可以使用带有 lib 的单个脚本标记的简单 html 文件),并且在父级中您只需要调用一个函数
sysend.proxy('https://example.com');
其中 example.com 需要有proxy.html
文件(您也可以使用自己的文件名和不同的文件名)小路)。There is simple way to use cross-domain anything, just create simple page that will be included as proxy iframe hosted on domain you try to access, send PostMessage to that iframe and inside iframe you do your LocalStorage database manipulation. Here is a link to article that do this with lcoalStorage. And here is demo that send message to different page in subdomain check the source code, it use iframe and PostMessage.
EDIT: New version of sysend.js library (used by above demo) use BroadcastChannel if browser support it, but still it require Iframe. Recent version also simplify using of Cross-Origin messages, you have html of the iframe in repo, that you can use (or you can use simple html file with single script tag with the lib) and in parent you just need to call one function
sysend.proxy('https://example.com');
where example.com need to haveproxy.html
file (you can also use your own filename and different path).默认情况下,Google Chrome 会阻止来自另一个域中的 iFrame 的 localStoage 访问,除非启用了第 3 方 cookie,iPhone 上的 Safari 也是如此...唯一的解决方案似乎是在不同的域上打开父域,然后发送到子域通过 window.postMessage 但在手机上看起来又丑又狡猾......
Google Chrome blocks localStoage access from an iFrame in another domain by default,unless 3rd party cookie is enabled and so does Safari on iPhone...the only solution seems to be opening the parent domain on a different domain and then sending to to the Child via window.postMessage but looks ugly and shifty on phones...
是的。方法如下:
用于在给定超级域的子域之间共享(例如
foo.example.com
vsbar.example.com
vsexample.com
),在这种情况下您可以使用一种技术。它可以应用于localStorage
、IndexedDB
、SharedWorker
、BroadcastChannel
等,所有这些都提供了共享功能同源页面,但由于某种原因,不尊重对document.domain
的任何修改,这会让它们直接使用超级域作为其来源。注意:此技术依赖于设置
document.domain
以允许不同子域上的 iframe 之间直接通信。该功能现已被弃用。 (截至 2021 年 4 月,它继续在所有主要浏览器中运行。从 Chrome v109 开始,该功能将被禁用除非Origin-Agent-Cluster: ?0
标头也被禁用已发送。)注意:请注意,此技术会删除阻止子域上的恶意脚本影响主域窗口的同源防御,反之亦然,可能会扩大 XSS 攻击的攻击面。共享托管还存在其他安全隐患 - 请参阅 有关详细信息,请参阅 MDN document.domain 页面。
(1) 选择一个数据所属的“主”域:即
https://foo.example.com
或https://bar.example.com 或
https://example.com
将保存您的 localStorage 数据。假设您选择https://example.com
。(2) 对所选域的页面正常使用 localStorage。
(3) 在所有其他 https://*.example.com 页面(其他域)上,使用 JavaScript 设置
document.domain = "example. com";
(始终是超级域)。然后还创建一个隐藏的,并将其导航到所选
https://example.com
域上的某个页面(< strong>什么页面并不重要,只要您可以在其中插入一小段 JavaScript 即可。如果您要创建网站,只需专门创建一个空页面即可。为此,如果您正在编写扩展程序或 Greasemonkey 样式的用户脚本,因此无法控制example.com
服务器上的页面,只需选择您能找到的最轻量级的页面。并将您的脚本插入其中。某种“未找到”页面可能会很好)。(4) 隐藏 iframe 页面上的脚本只需 (a) 设置
document.domain = "example.com";
,以及 (b) 完成此操作后通知父窗口。之后,父窗口就可以不受限制地访问iframe窗口及其所有对象了!因此,最小的 iframe 页面类似于:如果编写用户脚本,您可能不希望向
unsafeWindow
添加外部可访问的函数,例如iframeReady()
,因此改为通知主窗口用户脚本的更好方法可能是使用自定义事件:您可以通过向主页窗口添加自定义“iframeReady”事件的侦听器来检测该事件。
(注意:即使 iframe 的域已经是
example.com
,您也需要设置 document.domain =example.com
:为 document.domain 赋值会隐式设置源的port 为 null,并且两个端口必须匹配 iframe 及其父级才能被视为同源。请参阅此处的注释:https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)(5) 一旦隐藏的 iframe 通知其父窗口它已准备好,父窗口中的脚本就可以使用
iframe.contentWindow.localStorage
、iframe.contentWindow.indexedDB
、iframe.contentWindow.BroadcastChannel
、iframe.contentWindow.SharedWorker
而不是window.localStorage
、window.indexedDB
等等...所有这些对象的范围都将限定在所选的https://example.com
原点 - 因此它们将为您的所有页面拥有相同的共享原点!该技术最尴尬的部分是您必须等待 iframe 加载才能继续。因此,例如,您不能在 DOMContentLoaded 处理程序中轻松地开始使用 localStorage。此外,您可能需要添加一些错误处理来检测隐藏的 iframe 是否无法正确加载。
显然,您还应该确保隐藏的 iframe 在页面的生命周期内不会被删除或导航...OTOH 我不知道这样做的结果是什么,但很可能会发生不好的事情。
并且,需要注意的是:可以使用
Feature-Policy
标头来阻止设置/更改document.domain
,在这种情况下,该技术将不会被使用。可以按照描述使用。然而,这种技术有一个明显更复杂的概括,它不能被功能策略阻止,并且还允许完全不相关的域共享数据、通信和共享工人(即不仅仅是公共超级域的子域)。 @jcubic 已经在他们的答案中描述了它,即:
总体思路是,如上所述,您创建一个隐藏的 iframe 来提供正确的访问来源;但不是直接获取 iframe 窗口的属性,而是使用 iframe 内部的脚本来完成所有工作,并且仅使用
postMessage()
和在 iframe 和主窗口之间进行通信>addEventListener("消息",...)
.这是可行的,因为即使在不同来源的 Windows 之间也可以使用
postMessage()
。但它也明显更加复杂,因为您必须通过在 iframe 和主窗口之间创建的某种消息传递基础设施来传递所有内容,而不是直接在主窗口代码中使用 localStorage、IndexedDB 等 API。Yes. This is how:
For sharing between subdomains of a given superdomain (e.g.
foo.example.com
vsbar.example.com
vsexample.com
), there's a technique you can use in that situation. It can be applied tolocalStorage
,IndexedDB
,SharedWorker
,BroadcastChannel
, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect any modification todocument.domain
that would let them use the superdomain as their origin directly.NOTE: This technique depends on setting
document.domain
to allow direct communication between iframes on different subdomains. That functionality has now been deprecated. (As of April 2021 it continues to work in all major browsers however. From Chrome v109 the feature will be disabled unless anOrigin-Agent-Cluster: ?0
header is also sent.)NOTE: Be aware that this technique removes the same-origin defences that block malicious script on a subdomain from affecting the main-domain window, or visa versa, potentially broadening the attack surface for XSS attacks. There are other security implications for shared hosting as well - see the MDN document.domain page for details.
(1) Pick one "main" domain to for the data to belong to: i.e. either
https://foo.example.com
orhttps://bar.example.com
orhttps://example.com
will hold your localStorage data. Let's say you pickhttps://example.com
.(2) Use localStorage normally for that chosen domain's pages.
(3) On all other https://*.example.com pages (the other domains), use JavaScript to set
document.domain = "example.com";
(always the superdomain). Then also create a hidden<iframe>
, and navigate it to some page on the chosenhttps://example.com
domain (It doesn't matter what page, as long as you can insert a very little snippet of JavaScript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a Greasemonkey-style userscript and so don't have any control over pages on theexample.com
server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).(4) The script on the hidden iframe page need only (a) set
document.domain = "example.com";
, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:If writing a userscript, you might not want to add externally-accessible functions such as
iframeReady()
to yourunsafeWindow
, so instead a better way to notify the main window userscript might be to use a custom event:Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.
(NOTE: You need to set document.domain =
example.com
even if the iframe's domain is alreadyexample.com
: Assigning a value to document.domain implicitly sets the origin's port to null, and both ports must match for the iframe and its parent to be considered same-origin. See the note here: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use
iframe.contentWindow.localStorage
,iframe.contentWindow.indexedDB
,iframe.contentWindow.BroadcastChannel
,iframe.contentWindow.SharedWorker
instead ofwindow.localStorage
,window.indexedDB
, etc. ...and all these objects will be scoped to the chosenhttps://example.com
origin - so they'll have the this same shared origin for all of your pages!The most awkward part of this technique is that you have to wait for the iframe to load before proceeding. So you can't just blithely start using localStorage in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.
Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.
And, a caveat: setting/changing
document.domain
can be blocked using theFeature-Policy
header, in which case this technique will not be usable as described.However, there is a significantly more-complicated generalization of this technique, that can't be blocked by
Feature-Policy
, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). @jcubic already described it in their answer, namely:The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using
postMessage()
andaddEventListener("message",...)
.This works because
postMessage()
can be used even between different-origin Windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than just using the localStorage, IndexedDB, etc. APIs directly in your main window's code.