存储访问 API

存储访问 API 为在第三方上下文中加载的跨站点内容(即,嵌入在<iframe>中)提供了一种获取第三方 Cookie未分区状态的方法,而通常情况下,它只能在第一方上下文中(即,直接在浏览器选项卡中加载时)访问这些内容。

存储访问 API 适用于默认情况下阻止访问第三方 Cookie 和未分区状态以提高隐私性的用户代理(例如,为防止跟踪)。第三方 Cookie 和未分区状态有一些合法用途,我们希望即使在实施这些默认限制的情况下也能启用这些用途。例如,使用联合身份提供者 (IdP) 进行单点登录 (SSO),或在不同网站之间保留用户详细信息,如位置数据或查看偏好。

该 API 提供了允许嵌入资源检查其当前是否可以访问第三方 Cookie 的方法,如果不能,则可以向用户代理请求访问权限。

概念和用法

浏览器实施了几种存储访问功能和策略,限制对第三方 Cookie 和未分区状态的访问。这些功能和策略的范围从为每个顶级源下的嵌入资源提供唯一的 Cookie 存储空间(分区 Cookie)到在第三方上下文中加载资源时完全阻止 Cookie 访问。

围绕第三方 Cookie 和未分区状态阻止功能和策略的语义因浏览器而异,但核心功能相似。嵌入在第三方上下文中的跨站点资源无法访问与在第一方上下文中加载时相同的状态。这样做是为了良好的目的——浏览器供应商希望采取措施更好地保护其用户的隐私和安全。例如,减少用户在不同网站之间被跟踪的可能性,以及降低用户遭受跨站点请求伪造 (CSRF) 等漏洞的风险。

但是,嵌入的跨站点内容访问第三方 Cookie 和未分区状态有一些合法用途,而上述功能和策略会破坏这些用途。假设您有一系列提供不同产品访问权限的不同站点——heads-example.comshoulders-example.comknees-example.comtoes-example.com

或者,您可能出于本地化目的将内容或服务分离到不同的国家/地区域名——example.comexample.uaexample.br等——或者以其他方式。

您可能还有一些配套的实用程序站点,其中包含嵌入在所有其他站点中的组件,例如,提供 SSO (sso-example.com) 或通用个性化服务 (services-example.com)。这些实用程序站点希望通过 Cookie 与其嵌入的站点共享其状态。它们无法共享第一方 Cookie,因为它们位于不同的域中,并且第三方 Cookie 将不再在阻止它们的浏览器中工作。

在这种情况下,站点所有者通常会鼓励用户将其站点添加为例外,或完全禁用第三方 Cookie 阻止策略。希望继续与他们的内容交互的用户必须显著放宽对从所有嵌入源加载的资源(以及可能跨所有网站)的阻止策略。

存储访问 API 旨在解决此问题;嵌入的跨站点内容可以通过Document.requestStorageAccess()方法逐帧请求对第三方 Cookie 和未分区状态的无限制访问权限。它还可以通过Document.hasStorageAccess()方法检查其是否已获得访问权限。

未分区与分区 Cookie

需要注意的是,存储访问 API 仅需要提供对未分区第三方 Cookie 的访问权限。这意味着自早期 Web 以来以传统方式存储的 Cookie——在同一站点上设置的所有 Cookie 都存储在同一个 Cookie 存储区中。这与分区 Cookie 形成对比,在分区 Cookie 中,每个顶级站点下的嵌入资源都会获得唯一的 Cookie 存储空间,从而使通过这些 Cookie 跨站点跟踪用户成为不可能。

浏览器有各种机制来划分第三方 Cookie 访问权限,例如Firefox 完全 Cookie 保护具有独立分区状态的 Cookie (CHIPS)

当我们在存储访问 API 的上下文中讨论第三方 Cookie 时,我们隐含地指的是未分区的第三方 Cookie。

工作原理

需要合法访问第三方 Cookie 或未分区状态的嵌入内容可以使用存储访问 API 如下请求访问权限

  1. 它可以调用Document.hasStorageAccess()方法来检查其是否已拥有所需的访问权限。
  2. 如果不是,它可以通过Document.requestStorageAccess()方法请求访问权限。
  3. 根据浏览器,会以略微不同的方式询问用户是否授予请求嵌入的访问权限。
    • Safari 会显示针对以前未收到存储访问权限的所有嵌入内容的提示。
    • Firefox 仅在源请求了超过阈值数量的站点的存储访问权限后才会提示用户。
    • Chrome 会显示针对以前未收到存储访问权限的所有嵌入内容的提示。但是,如果嵌入内容和嵌入站点属于同一相关网站集,它将自动授予访问权限并跳过提示。
  4. 是否授予访问权限取决于内容是否满足所有安全要求——有关一般要求,请参阅安全措施,有关某些浏览器特定的安全要求,请参阅浏览器特定差异requestStorageAccess()的基于Promise的特性允许您运行代码来处理成功和失败的情况。
    • 现代规范行为规定,访问权限是逐帧授予的——每个单独的内容嵌入都会默认阻止其第三方 Cookie 访问权限,并且需要调用requestStorageAccess()来选择加入访问。如果内容嵌入已获得访问权限,并且同站点嵌入随后调用requestStorageAccess(),则其 Promise 将自动履行。但它们仍然需要选择加入。
    • “默认情况下阻止”行为的唯一例外情况是,当内容嵌入成功执行requestStorageAccess(),但随后执行同源导航(例如重新加载自身)时。在这种情况下,存储访问权限将从之前的导航中继承。
    • 在较旧的规范版本中,访问权限是逐页的(Safari 是唯一仍然使用此模型的浏览器)。当一个嵌入通过requestStorageAccess()获得第三方 Cookie 访问权限时,所有其他同站点嵌入都会自动获得访问权限。从安全角度来看,这不是理想的行为——例如,如果shop.example.com嵌入locator.users.com以允许用户在购物时使用其位置信息,并且locator.users.com调用requestStorageAccess(),则shop.example.com及其嵌入的任何其他站点都将能够访问其 Cookie,但也能够访问来自private.users.com的 Cookie,而这并非预期嵌入的。详细了解此更改背后的动机。
  5. 一旦授予访问权限,权限密钥就会与结构<顶级站点,嵌入站点>一起存储在浏览器中。例如,如果嵌入站点是embedder.com,并且嵌入是locator.example.com,则密钥将为<embedder.com, example.com>。同站点嵌入 (docs.example.comprofile.example.com等) 随后将能够调用requestStorageAccess(),并且 Promise 将自动履行,如前所述。
    • 较旧的规范版本使用更具体的权限密钥结构<顶级站点,嵌入源>,这意味着同站点、跨源嵌入与权限密钥不匹配,并且必须单独完成整个过程。

注意:在顶级站点的 Cookie 已分区的情况下,不需要存储访问 API,因为默认情况下共享 Cookie 没有隐私风险。

安全措施

一些不同的安全措施可能会导致Document.requestStorageAccess()调用失败。如果您遇到请求无法正常工作的问题,请查看以下列表

  1. 该调用必须与用户手势(瞬时激活)相关联,例如轻触或点击。这可以防止页面上的嵌入内容向浏览器或用户发送过多的访问请求。请注意,如果以下情况则不需要此操作
    • 已授予使用 API 的权限,例如,通过另一个同站点资源调用requestStorageAccess()
    • 调用方是顶级文档或与顶级文档同站点。在这种情况下,可能根本不需要调用requestStorageAccess()
  2. 文档和顶级文档不得具有null源。
  3. 从未以第一方身份进行交互的源没有第一方存储的概念。从用户的角度来看,他们仅与该源存在第三方关系。如果浏览器检测到用户最近未在第一方上下文中与嵌入内容进行交互(在 Firefox 中,“最近”表示 30 天内),则会自动拒绝访问请求。
  4. 文档的窗口必须是安全上下文
  5. 出于安全原因,沙盒化的 <iframe> 默认情况下无法获得存储访问权限。因此,API 还添加了 allow-storage-access-by-user-activation 沙盒标记。嵌入网站需要添加此标记才能使存储访问请求成功,并与 allow-scriptsallow-same-origin 结合使用,以允许其执行脚本以调用 API 并将其在可以拥有 Cookie/状态的来源中执行。
    html
    <iframe
      sandbox="allow-storage-access-by-user-activation
                    allow-scripts
                    allow-same-origin"></iframe>
    
  6. 此功能的使用可能会被服务器上设置的 storage-access 权限策略 阻止。

注意:文档可能还需要通过其他浏览器特定的检查。例如:允许列表、阻止列表、设备上分类、用户设置、反 点击劫持 启发式方法或提示用户进行明确许可。

浏览器特定差异

尽管 API 表面相同,但由于其存储访问策略的不同,使用存储访问 API 的网站应该预期在不同浏览器之间接收到的第三方 Cookie 访问级别和范围会有所不同。

Chrome

  • Cookie 必须显式设置 SameSite=None,因为 Chrome 的默认值为 SameSite=LaxSameSite=None 是 Firefox 和 Safari 的默认值)。
  • Cookie 必须设置 Secure 属性。
  • 在经过 30 天的浏览器使用且没有用户交互后,存储访问权限会被逐步淘汰。与嵌入内容的交互会将此限制延长另外 30 天。当调用 Document.requestStorageAccessFor() 时,不会发生这种情况,因为用户已在页面上。

Firefox

  • 如果嵌入来源 tracker.example 已经在顶级来源 foo.example 上获得了第三方 Cookie 访问权限,并且用户在不到 30 天内再次访问来自 foo.example 的页面并嵌入来自 tracker.example 的页面,则嵌入来源在加载时将立即拥有第三方 Cookie 访问权限。
  • 存储访问权限在 30 个日历日后被逐步淘汰。

阻止跟踪 Cookie 的 Firefox 新存储访问策略的文档包括 存储访问权限范围的详细说明

Safari

  • 在经过 30 天的浏览器使用且没有用户交互后,存储访问权限会被逐步淘汰。成功使用存储访问 API 会重置此计数器。

示例

API 方法

Document.hasStorageAccess()

返回一个 Promise,该 Promise 解析为一个布尔值,指示文档是否可以访问第三方 Cookie。

Document.hasUnpartitionedCookieAccess()

Document.hasStorageAccess() 的新名称。

Document.requestStorageAccess()

允许在第三方上下文中加载的内容(即,嵌入在 <iframe> 中)请求访问第三方 Cookie 和未分区状态;返回一个 Promise,如果访问被授予则解析,如果访问被拒绝则拒绝。

Document.requestStorageAccessFor() 实验性

存储访问 API 的一项拟议扩展,允许顶级站点代表来自同一 相关网站集 中其他站点的嵌入内容请求第三方 Cookie 访问权限。返回一个 Promise,如果访问被授予则解析,如果访问被拒绝则拒绝。

注意:用户交互会传播到这些方法返回的 Promise,允许调用方执行需要用户交互的操作,而无需第二次点击。例如,调用方可以在解析的 Promise 中打开一个弹出窗口,而不会触发 Firefox 的弹出窗口阻止程序。

对其他 API 的添加

Permissions.query()"storage-access" 功能名称

在支持的浏览器中,这可以查询是否已普遍授予第三方 Cookie 访问权限,即授予另一个同站点嵌入。如果是这样,您可以无需用户交互即可调用 requestStorageAccess(),并且 Promise 将自动解析。

Permissions.query()"top-level-storage-access" 功能名称 实验性

用于查询是否已通过 requestStorageAccessFor() 授予访问第三方 Cookie 的权限的单独功能名称。如果是这样,您无需再次调用 requestStorageAccessFor()

规范

规范
存储访问 API
将存储访问 API (SAA) 扩展到非 Cookie 存储

浏览器兼容性

api.Document.hasStorageAccess

BCD 表仅在启用 JavaScript 的浏览器中加载。

api.Document.hasUnpartitionedCookieAccess

BCD 表仅在启用 JavaScript 的浏览器中加载。

api.Document.requestStorageAccess

BCD 表仅在启用 JavaScript 的浏览器中加载。

api.Document.requestStorageAccessFor

BCD 表仅在启用 JavaScript 的浏览器中加载。

api.Permissions.permission_storage-access

BCD 表仅在启用 JavaScript 的浏览器中加载。

另请参阅