跨子域使用localStorage

157
我正在将浏览器中的cookie替换为支持它的浏览器上的localStorage(除IE外)。问题是site.examplewww.site.example存储它们自己单独的localStorage对象。我相信www被认为是一个子域(如果你问我的话,这是一个愚蠢的决定)。如果用户最初在site.example上,并决定在下一次访问时输入www.site.example,那么她所有的个人数据都将无法访问。如何使所有“子域”共享与主域相同的localStorage?

5
Firefox和IE8支持在用户指定的域名下存储持久化数据。例如,在Firefox中,您可以使用globalStorage['site.com'],这将对www.site.com和site.com可访问。我仍然没有弄清楚如何在Chrome的实现中执行此操作。 - JoJo
19
考虑使用以下之一 -- 将访问带有“www”子域名的所有用户重定向到无子域名域名,或者反过来。 - Elad Nava
1
我很久以前创建了一篇文章:跨域LocalStorage - jcubic
8个回答

136

以下是我跨域使用它的方式...

  • 从您的父域 - 比如说parent.example - 使用一个iframe
  • 然后在每个child.example域中,只需要向您的parent.example iframe发送postMessage消息
  • 您只需要设置一种协议来解释如何解读您的postMessage消息以与parent.example iframe通信。

2
这是真正的答案,而不是已勾选的答案。我自己做过这个,但也创建了一个方便的回调包装器,使用postMessage。 - King Friday
2
同意之前评论者的观点。这应该可以工作。但它大多是一种变通方法 :) 看起来 localStorage 规范需要更加灵活。 - ValeriiVasin
5
这是一篇好的文章,其中包含一些示例代码,解释了这种方法:https://jcubic.wordpress.com/2014/06/20/cross-domain-localstorage/。 - Todd Price
6
请注意,仅在第三方Cookie未被禁用时才有可能实现此操作:https://dev59.com/L2TWa4cB1Zd3GeqPBkVk#44097269 - maxeh
12
苹果已经更新了桌面和移动版Safari 7+的默认设置,以阻止第三方数据。现在的选项被称为“阻止cookie和其他网站数据”,它指的是像本地存储这样的内容,这些内容现在完全按域隔离。这种方法在Safari中不起作用。 - Aranganathan
显示剩余14条评论

57
如果只是为了解决这个问题而使用iframe和postMessage解决方案,我认为使用不带子域名的cookie存储数据可能会更加简单(无论是从代码还是计算方面)。如果在加载时localStorage中没有数据,则从cookie中获取数据。

优点:

  • 不需要额外设置iframe和postMessage。

缺点:

  • 将使数据在所有子域名(不仅仅是www)中可用,因此如果您不信任所有子域名,则可能无法使用。
  • 每次请求都会将数据发送到服务器。不太好,但根据您的情况,可能仍比iframe/postMessage解决方案更简单。
  • 如果是这样做的话,为什么不直接使用cookie呢?这取决于您的上下文。
  • 对于整个域中的所有cookie,最大大小为4K(感谢Blake在评论中指出)。

我同意其他评论者的看法,这似乎应该是localStorage的可规定选项,因此不需要使用work-arounds。


47
反对意见:最大Cookie大小为4k - Blake Miller
31
同样,就像我通过艰苦的方式学到的那样,4k的限制是指单个域名下所有cookie大小之和的限制,而不是每个cookie的大小。 - Blake Miller
其他缺点:
  • Cookie 更有可能被广告拦截器屏蔽
  • Cookie 旨在用于服务器和客户端之间共享小型数据。如果服务器没有使用您存储在 Cookie 中的数据,则这是一种误用。
- Enno
另一个缺点是:在Safari和Brave等浏览器上,从前端设置的cookie的最长生命周期为7天。 - Rishabh Poddar

41

我建议将site.example重定向到www.site.example,以保持一致性并避免出现此类问题。

此外,请考虑使用跨浏览器解决方案,例如PersistJS,它可以使用每个浏览器的本地存储。


2
@JoJo 有几种重定向的方法,例如通过发送 Location 头部、通过 <meta> HTML 标签或甚至是通过 window.location 的 JS。 - Sony Santos
1
这只是回避问题。请参考Mayank的答案作为正确答案。 - King Friday
1
+1 @avoiding,此外这对于其他情况是无关紧要的 - 就像我在这里的情况一样,lang1.domain.com - lang2.domain.com。 - r---------k
你可以使用JS来实现:if (!/^www/.test(location.hostname)) location.href=location.href.replace('://','://www.') - oriadam
1
@r---------k 不是无关紧要的,将您的 lang1.domain 重定向到 domain.com/lang1/,重写为 domain.com?l=lang1,最后让您的服务器脚本处理 GET 请求以发送适当的文件。 - mikakun
显示剩余3条评论

19

设置主域中的Cookie:

document.cookie = "key=value;domain=.mydomain.example"

然后从任何主域或子域中获取数据,并将其设置在localStorage上。


7
那么,所有的POST请求都将面临跨站请求伪造攻击的风险。 - Murat Tutumlu
2
@MuratTutumlu,您能否详细解释一下您的答案? - Kevin O.

12

这是如何实现的:

[2020年11月更新: 此解决方案依赖于能够设置document.domain。不幸的是,现在已经废弃了这种能力。此外,请注意,这样做会消除域和子域之间的“防火墙”,容易受到XSS攻击或其他恶意脚本的攻击,并且对共享托管具有进一步的安全影响,如MDN页面所述。 2022年9月更新: 从Chrome v109开始,只有在发送Origin-Agent-Cluster: ?0头的页面上才能设置document.domain]

对于给定超级域(例如example.com)的子域之间的共享,您可以使用一种技术来解决这个问题。它可以应用于localStorageIndexedDBSharedWorkerBroadcastChannel等所有提供同源页面之间共享功能的内容,但由于某些原因不遵守任何修改document.domain的规则,该规则允许它们直接使用超级域作为其来源。

(1) 选择一个“主”域,将数据归属于该域:即https://example.comhttps://www.example.com将保存您的localStorage数据。假设您选择https://example.com

(2) 对于已选择的域的页面,请正常使用localStorage。

(3) 在所有https://www.example.com页面(另一个域)上,使用javascript设置document.domain = "example.com";。然后还要创建一个隐藏的<iframe>,并将其导航到所选的https://example.com域上的某个页面(不管是哪个页面,只要您可以在其中插入非常小的javascript片段即可。如果您正在创建网站,请专门为此目的创建一个空页面。如果您正在编写扩展程序或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 隐式地将源的端口设置为 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.localStoragewindow.indexedDB等等...并且所有这些对象都将被限定在所选择的 https://example.com 来源 - 因此它们将为您的所有页面具有相同的共享来源!
这种技术最棘手的部分是您必须等待 iframe 加载完成后才能继续。例如,您不能在 DOMContentLoaded 处理程序中轻率地开始使用 localStorage。此外,您可能希望添加一些错误处理来检测隐藏的 iframe 是否未正确加载。
显然,您还应确保在页面的生命周期内不会删除或导航到隐藏的 iframe...另一方面,我不知道这将会产生什么结果,但非常可能会发生不好的事情。
并且,一个警告:设置/更改document.domain可能会被Feature-Policy头阻止,在这种情况下,此技术将无法按描述使用。
然而,有一种更为复杂的技术可以实现此目的,它无法被Feature-Policy阻止,并且还允许完全不相关的域共享数据、通信和共享工作者(即不仅是共同超域的子域)。@Mayank Jain 在他们的答案中已经描述了这个概念,即:
一般的想法是,就像上面所述,创建一个隐藏的 iframe 来提供正确的访问来源;但是,您不是直接获取 iframe 窗口的属性,而是使用 iframe 内部的脚本来完成所有工作,并且只使用postMessage()addEventListener("message",...)在 iframe 和主窗口之间进行通信。

这是可行的,因为即使在不同来源的窗口之间,也可以使用postMessage()。但这也更加复杂,因为您必须通过一些消息传递基础设施来传递所有内容,该基础设施是在iframe和主窗口之间创建的,而不能直接在主窗口代码中使用localStorage、IndexedDB等API。


此后,父窗口可以访问 iframe 窗口和其中所有对象,而且没有任何限制。这里的“没有任何限制”部分让我感到担忧。对于类似的回答,也存在类似的担忧(参见 https://dev59.com/8G855IYBdhLWcg3wuG6W#63602446)。您是否考虑过这种方法的安全性影响?假定 www 和非 www 网站有不同的内容,否则为什么不进行重定向,这也是 OP 提出这个问题的原因。但是,通过这种方法,一个子域名的所有 XSS 漏洞将与另一个子域名共享,从而增加了风险。 - Colm Bhandal
1
@Colm,从问题的措辞中我得出的印象是site.comwww.site.com访问相同的页面,因此可以通过重定向来解决。使用本答案中的技术确实存在安全隐患,但在许多情况下,子域可能已被用于与安全无关的原因...因此,是否采用这种技术会引入不可接受的风险需要根据网站的具体情况进行评估。我在上面的答案中添加了有关安全性的注释。 - Doin
非常感谢,我赞扬您为社区所做的贡献。 - Colm Bhandal

4

4
我看了一下,但似乎在Safari上无法使用。https://github.com/ofirdagan/cross-domain-local-storage/issues/10 - Andris Zalitis

0

-4
这是我为我的网站解决它的方式。我将所有没有www的页面重定向到www.site.example。这样,它将始终使用www.site.example的本地存储。
在根目录下创建一个.htaccess文件(如果您还没有该文件),并添加以下内容:
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]

11
我很想给这个回答点踩,但我不会这样做,因为它可能有助于原帖作者的使用情况,但对于那些想要在myapp.com、developers.myapp.com和support.myapp.com之间保持会话的人来说,这个答案并不好。 - Don Omondi
嘿@DonOmondi,如果您能帮我提供您所建议的链接,我将不胜感激! - Ayush Baheti
4
楼主问道:“如何在子域名之间使用localStorage”,您的回答是“将www重定向到非www”,这两件事情非常不同,但只有当特定子域名为“www.abc.com”时才适用,对于一般情况下,其他一些答案更加实用。 - Don Omondi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接