【问题标题】:Can HTML5 databases and localStorage be shared across subdomains?HTML5 数据库和 localStorage 可以跨子域共享吗?
【发布时间】:2011-05-09 20:00:03
【问题描述】:

我正在尝试使用 Safari 跨子域共享数据。我想使用 HTML5 数据库(特别是 localStorage,因为我的数据只是键值对)。但是,似乎无法从 sub.domain.com 访问存储到 domain.com 的数据(反之亦然)。在这种情况下有没有办法共享单个数据库?

【问题讨论】:

标签: database html dns subdomain local-storage


【解决方案1】:

有一种使用跨域的简单方法,只需创建简单的页面,该页面将作为代理 iframe 包含在您尝试访问的域上,发送 PostMessage 到那个 iframe 和 iframe 里面你做你的 LocalStorage 数据库操作。这是article that do this with lcoalStorage 的链接。这里是demo that send message to different page in subdomain查看源代码,它使用iframe和PostMessage。

编辑:如果浏览器支持,新的version of sysend.js library(由上面的演示使用)使用 BroadcastChannel,但它仍然需要 Iframe。最近的版本还简化了跨域消息的使用,您在 repo 中有 iframe 的 html,您可以使用(或者您可以使用带有 lib 的单个脚本标签的简单 html 文件)并且在父级中您只需要调用一个函数sysend.proxy('https://example.com'); 其中 example.com 需要有 proxy.html 文件(您也可以使用自己的文件名和不同的路径)。

【讨论】:

  • 只有我一个人还是那种很臭的解决方案?听起来很笨拙。 (没有downvote ...只是说)。
  • 这并不是真正的笨拙,因为 PostMessage 是为了跨域通信而创建的。问题是在不同主机上保留/维护 IFRAME 内容。假设您正在使用 3rd-party 购物车解决方案……并非总是可以在他们的域上为您的 PostMessage 接收 IFRAME 创建目标页面。此外,请查看新的双向通道消息 (support starts at IE10)。
  • 可能是这个话题的最佳解决方案!原生且高效。
  • 这是针对不同域的最佳通用解决方案。但是,当您像 OP 那样只有子域时,有一个更简单的解决方案:您仍然使用代理 iframe 来保存正确的数据访问来源,而不是 postMessage(),您只需在两个页面上设置 document.domain = superdomain,然后他们可以直接进行交互,然后在您的主页上使用iframe.contentWindow.localStorage 而不是window.localStorage!我想这已经足够不同了,我会把它作为单独的答案发布......
  • 如果“阻止第三方 cookie”设置在浏览器中打开,此解决方案将不起作用。它在 safari 中默认启用
【解决方案2】:

默认情况下,Google Chrome 会阻止来自另一个域中的 iFrame 的 localStoage 访问,除非启用了第 3 方 cookie,iPhone 上的 Safari 也是如此......唯一的解决方案似乎是在不同域中打开父域,然后发送到通过 window.postMessage 发送给孩子,但在手机上看起来又丑又狡猾...

【讨论】:

  • Chrome 中有设置来改变这种行为吗?
【解决方案3】:

2016 年更新

来自 Zendesk 的 library 为我工作。

示例:

集线器

// Config s.t. subdomains can get, but only the root domain can set and del
CrossStorageHub.init([
  {origin: /\.example.com$/,            allow: ['get']},
  {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);

注意$ 以匹配字符串的结尾。上例中的正则表达式将匹配 valid.example.com 等来源,但不匹配 invalid.example.com.malicious.com

客户

var storage = new CrossStorageClient('https://store.example.com/hub.html');

storage.onConnect().then(function() {
  return storage.set('newKey', 'foobar');
}).then(function() {
  return storage.get('existingKey', 'newKey');
}).then(function(res) {
  console.log(res.length); // 2
}).catch(function(err) {
  // Handle error
});

查看https://stackoverflow.com/a/39788742/5064633

【讨论】:

    【解决方案4】:

    是的。方法如下:

    对于给定超级域的子域之间的共享(例如 foo.example.com vs bar.example.com vs example.com),您可以在这种情况下使用一种技术。它可以应用于localStorageIndexedDBSharedWorkerBroadcastChannel 等,所有这些都提供同源页面之间的共享功能,但由于某种原因,不尊重对document.domain 的任何修改会让他们直接使用超级域作为他们的来源。

    注意:此技术依赖于设置 document.domain 以允许不同子域上的 iframe 之间的直接通信。该功能现已弃用。 (尽管自 2021 年 4 月起,它仍可在所有主要浏览器中运行,并且可能会保留下来以实现向后兼容性)。

    (1) 为数据选择一个“主”域:即 https://foo.example.comhttps://bar.example.comhttps://example.com 将保存您的 localStorage 数据。假设您选择https://example.com

    (2) 对所选域的页面正常使用 localStorage。

    (3) 在所有其他 https://*.example.com 页面(other 域)上,使用 javascript 设置 document.domain = "example.com";(始终为超级域)。然后还创建一个隐藏的<iframe>,并将其导航到所选https://example.com 域上的一些 页面(没关系什么 页面,只要您可以在其中插入少量的 javascript sn-p。如果您正在创建站点,只需为此目的专门制作一个空页面即可。扩展程序或 Greasemonkey 样式的用户脚本,因此无法控制 example.com 服务器上的页面,只需选择您能找到的最轻量级的页面并将脚本插入其中即可。某种“未找到”页面可能会很好)。

    (4) 隐藏 iframe 页面上的脚本只需要 (a) 设置 document.domain = "example.com";,并且 (b) 完成后通知父窗口。之后,父窗口可以不受限制地访问 iframe 窗口及其所有对象!所以最小的 iframe 页面是这样的:

    <!doctype html>
    <html>
    <head>
      <script>
        document.domain = "example.com";
        window.parent.iframeReady();  // function defined & called on parent window
      </script>
    </head>
    <body></body>
    </html>
    

    如果编写用户脚本,您可能不想将iframeReady() 等外部可访问函数添加到unsafeWindow,因此通知主窗口用户脚本的更好方法可能是使用自定义事件:

        window.parent.dispatchEvent(new CustomEvent("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.localStorageiframe.contentWindow.indexedDBiframe.contentWindow.BroadcastChanneliframe.contentWindow.SharedWorker 而不是 window.localStorage、@987654346 @ 等...所有这些对象的范围都将限定为所选的 https://example.com 来源 - 因此它们将为您的所有页面拥有相同的共享来源!

    此技术最尴尬的部分是您必须等待 iframe 加载后才能继续。因此,例如,您不能只是愉快地开始在 DOMContentLoaded 处理程序中使用 localStorage。此外,您可能需要添加一些错误处理来检测隐藏的 iframe 是否无法正确加载。

    显然,您还应该确保隐藏的 iframe 在页面的生命周期内没有被删除或导航... OTOH 我不知道结果会是什么,但很可能会发生不好的事情。

    并且,需要注意的是:设置/更改document.domain 可以使用Feature-Policy 标头阻止,在这种情况下,该技术将无法如所述使用。


    但是,这种技术有一个更为复杂的概括,Feature-Policy 无法阻止它,而且它还允许完全不相关的域共享数据、通信和共享工作人员(即不仅仅是公共超级域的子域)。 @jcubic 已经在他们的回答中描述了它,即:

    一般的想法是,就像上面一样,您创建一个隐藏的 iframe 来提供正确的访问来源;但不是直接获取 iframe 窗口的属性,而是使用 iframe 内的脚本来完成所有工作,并且只使用 postMessage()addEventListener("message",...) 在 iframe 和主窗口之间进行通信。

    这是因为postMessage() 甚至可以在不同来源的窗口之间使用。但它也明显更加复杂,因为您必须通过在 iframe 和主窗口之间创建的某种消息传递基础架构传递所有内容,而不是直接在主窗口的代码中使用 localStorage、IndexedDB 等 API。

    【讨论】:

    • 如果浏览器中的“阻止第三方cookies”设置开启,此解决方案将不起作用。在 safari 中默认启用它
    • @vishesh 这个答案不涉及cookies,但是很可能Safari设置也阻止了其他事情。有更多信息会很好。 究竟是什么不起作用?设置document.domain会引发错误吗?从父窗口访问 iframe 是否被阻止?还是仅当您尝试使用 localStorage 时才会出现问题?
    • 是的,浏览器禁用对本地存储的访问,就像它们对 cookie 一样。这由相同的“阻止第三方 cookie”设置控制。 document.domain 已弃用,它可能已经在某些浏览器中无法使用。只有当 iframe 想要访问它自己的本地存储时才会出现问题。
    • @vihesh 如果浏览器阻止 iframe 访问甚至 它自己的本地存储,那么询问您是否可以跨子域共享对它的访问没有多大意义。 ...还是只有 iframe,而不是顶级窗口?还是只有在您更改document.domain 时才会发生这种情况?无论如何,我会欣然承认有许多事情会干扰这个解决方案。然而,当它工作时,它可以比其他基于postMessage 的解决方案简单得多,因为使用它你可以直接访问 localStorage/indexedDB/etc API。
    • @vihesh 我在 Firefox 上运行的 Greasemonkey 脚本中成功使用了它,顺便说一句。
    猜你喜欢
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    • 2011-11-27
    相关资源
    最近更新 更多