浏览器、用户设置和存储分区会阻止第三方 Cookie,这对依赖于嵌入式情境中的 Cookie 和其他存储空间的网站和服务来说,是一次考验,因为这些网站和服务需要 Cookie 和其他存储空间来实现身份验证等用户体验历程。借助 Storage Access API (SAA),这些用例可以继续正常运行,同时尽可能限制跨网站跟踪。
实现状态
Storage Access API 适用于所有主流浏览器,但各浏览器之间存在细微的实现差异。本文的相关部分已重点介绍了这些差异。
Storage Access API 是什么?
Storage Access API 是一种 JavaScript API,可让 iframe 在浏览器设置否认访问权限时请求存储空间访问权限。如果嵌入的用例依赖于加载跨网站资源,则可以根据需要使用此 API 向用户请求访问权限。
如果存储空间请求获得批准,iframe 将能够访问其未分区的 Cookie 和存储空间,用户以顶级网站的身份访问该 iframe 时也能访问这些 Cookie 和存储空间。
Storage Access API 允许以对最终用户造成的负担最小的方式提供特定的非分区 Cookie 和存储空间访问权限,同时仍会阻止通常用于用户跟踪的通用非分区 Cookie 和存储空间访问权限。
使用场景
某些第三方嵌入内容需要访问未分区的 Cookie 或存储空间,才能为用户提供更好的体验。如果限制第三方 Cookie 并启用存储空间分区,则无法提供此类体验。
用例包括:
- 需要登录会话详细信息的嵌入式评论微件。
- 需要登录会话详细信息的社交媒体“赞”按钮。
- 需要登录会话详细信息的嵌入式文档。
- 为嵌入的视频提供的优质体验(例如,不向已登录的用户展示广告,或了解用户对字幕的偏好设置,或限制某些视频类型)。
- 嵌入式付款系统。
其中许多用例涉及在嵌入的 iframe 中保留登录访问权限。
何时应使用 Storage Access API 而非其他 API
Storage Access API 是使用未分区的 Cookie 和存储空间的替代方案之一,因此了解何时应使用此 API 而非其他 API 非常重要。它适用于以下两种情况都成立的用例:
- 用户将与嵌入的内容互动,也就是说,该内容不是被动 iframe 或隐藏 iframe。
- 用户在顶级情境中访问了嵌入的来源,即该来源未嵌入到其他网站中。
有适用于各种用例的替代 API:
- 借助具有独立分区状态的 Cookie (CHIPS),开发者可以选择将 Cookie 存储到“分区”存储空间,每个顶级网站都有单独的 Cookie Jar。例如,第三方网络聊天 widget 可能需要设置 Cookie 才能保存会话信息。会话信息会按网站保存,因此无需在嵌入该微件的其他网站上访问该微件设置的 Cookie。当嵌入的第三方 widget 依赖于在不同来源之间共享相同信息时(例如已登录的会话详情或偏好设置),Storage Access API 非常有用。
- 存储分区是一种方法,可让跨网站 iframe 使用现有的 JavaScript 存储机制,同时按网站划分底层存储空间。这样可以防止一个网站中的嵌入式存储空间被其他网站上的相同嵌入内容访问。
- 相关网站集 (RWS) 是一种组织声明网站之间关系的方式,以便浏览器允许出于特定目的进行有限的分区 Cookie 和存储访问。网站仍需要使用 Storage Access API 请求访问权限,但对于该集合中的网站,系统无需提示用户即可授予访问权限。
- Federated Credential Management (FedCM) 是一种可保护隐私的联合身份服务方法。Storage Access API 用于处理在登录后访问未分区的 Cookie 和存储空间。对于某些用例,FedCM 提供了 Storage Access API 的替代解决方案,并且由于其具有更侧重于登录的浏览器提示,因此可能更为合适。不过,采用 FedCM 通常需要对代码进行额外更改,例如支持其 HTTP 端点。
- 还有防欺诈、广告相关和衡量 API,而 Storage Access API 不适用于解决这些问题。
使用 Storage Access API
Storage Access API 有两种基于 Promise 的方法:
Document.hasStorageAccess()
(从 Chrome 125 开始,也以新名称Document.hasUnpartitionedCookieAccess()
提供)Document.requestStorageAccess()
它还与 Permissions API 集成。这样,您就可以检查第三方情境中的存储空间访问权限状态,这表示系统是否会自动授予对 document.requestStorageAccess()
的调用:
使用 hasStorageAccess()
方法
网站首次加载时,可以使用 hasStorageAccess()
方法检查是否已授予对第三方 Cookie 的访问权限。
// Set a hasAccess boolean variable which defaults to false.
let hasAccess = false;
async function handleCookieAccessInit() {
if (!document.hasStorageAccess) {
// Storage Access API is not supported so best we can do is
// hope it's an older browser that doesn't block 3P cookies.
hasAccess = true;
} else {
// Check whether access has been granted using the Storage Access API.
// Note on page load this will always be false initially so we could be
// skipped in this example, but including for completeness for when this
// is not so obvious.
hasAccess = await document.hasStorageAccess();
if (!hasAccess) {
// Handle the lack of access (covered later)
}
}
if (hasAccess) {
// Use the cookies.
}
}
handleCookieAccessInit();
只有在 iframe 文档调用 requestStorageAccess(),
后,系统才会向其授予存储空间访问权限,因此 hasStorageAccess()
最初始终会返回 false,除非同一 iframe 中的另一个同源文档已获得访问权限。授予的权限会在 iframe 内的同源导航中保留,这是为了在授予对需要在 HTML 文档的初始请求中包含 Cookie 的网页的访问权限后,允许重新加载。
使用 requestStorageAccess()
如果 iframe 没有访问权限,则可能需要使用 requestStorageAccess()
方法请求访问权限:
if (!hasAccess) {
try {
await document.requestStorageAccess();
} catch (err) {
// Access was not granted and it may be gated behind an interaction
return;
}
}
首次请求此权限时,用户可能需要通过浏览器提示批准此访问权限,之后该 Promise 将解析,或者会拒绝(如果使用 await
,则会导致异常)。
为防止滥用,此浏览器提示仅会在用户互动后显示。因此,最初需要从用户激活的事件处理脚本调用 requestStorageAccess()
,而不是在 iframe 加载时立即调用:
async function doClick() {
// Only do this extra check if access hasn't already been given
// based on the hasAccess variable.
if (!hasAccess) {
try {
await document.requestStorageAccess();
hasAccess = true; // Can assume this was true if requestStorageAccess() did not reject.
} catch (err) {
// Access was not granted.
return;
}
}
if (hasAccess) {
// Use the cookies
}
}
document.querySelector('#my-button').addEventListener('click', doClick);
如果您需要使用本地存储空间而非 Cookie,可以执行以下操作:
let handle = null;
async function doClick() {
if (!handle) {
try {
handle = await document.requestStorageAccess({localStorage: true});
} catch (err) {
// Access was not granted.
return;
}
}
// Use handle to access unpartitioned local storage.
handle.localStorage.setItem('foo', 'bar');
}
document.querySelector('#my-button').addEventListener('click', doClick);
权限提示
在大多数情况下,当用户首次点击该按钮时,浏览器提示会自动显示(通常在地址栏中)。以下屏幕截图显示了 Chrome 提示的示例,但其他浏览器的界面也类似:
在某些情况下,浏览器可能会跳过提示,并自动提供权限:
- 如果在接受提示后的过去 30 天内使用过相应网页和 iframe。
- 如果嵌入的 iframe 属于相关网站集。
- 如果 FedCM 用作存储空间访问的信任信号。
- 在 Firefox 中,如果是已知网站(您在顶级页面与之互动过的网站),系统在前五次尝试时也会跳过提示。
或者,在某些情况下,系统可能会自动拒绝该方法,而不会显示提示:
- 如果用户之前未以顶级文档(而非 iframe)的形式访问并与拥有 iframe 的网站互动过。这意味着,Storage Access API 仅适用于用户之前在第一方环境中访问过的嵌入式网站。
- 如果在用户互动事件之外调用
requestStorageAccess()
方法,且未在互动后事先批准提示。
虽然系统会在用户首次使用时提示,但在 Chrome 和 Firefox 中,后续访问时无需提示即可解析 requestStorageAccess()
,也不需要用户互动。请注意,Safari 始终需要用户互动。
由于系统可能会在不提示或用户互动的情况下授予 Cookie 和存储空间访问权限,因此在支持此功能的浏览器(Chrome 和 Firefox)中,通常可以在用户互动之前通过在网页加载时调用 requestStorageAccess()
来获取未分区的 Cookie 或存储空间访问权限。这样,您或许可以立即访问未分区的 Cookie 和存储空间,并在用户与 iframe 互动之前提供更完整的体验。在某些情况下,这比等待用户互动能提供更好的用户体验。
将 FedCM 用作 SAA 的信任信号
FedCM(联合身份管理)是一种可保护隐私的联合身份服务(例如“使用...登录”)方法,不依赖于第三方 Cookie 或导航重定向。
当用户登录包含来自第三方身份提供商 (IdP) 的某些嵌入内容且具有 FedCM 的依赖方 (RP) 时,嵌入的 IdP 内容可以自动获得对其自己的顶级未分区 Cookie 的存储访问权限。若要使用 FedCM 启用自动存储空间访问权限,必须满足以下条件:
- FedCM 身份验证(用户登录状态)必须处于有效状态。
- RP 已通过设置
identity-credentials-get
权限来选择启用此功能,例如:
<iframe src="https://idp.example" allow="identity-credentials-get"></iframe>
例如,idp.example
iframe 嵌入在 rp.example
中。当用户使用 FedCM 登录时,idp.example
iframe 可以请求对其自己的顶级 Cookie 的存储访问权限。
rp.example
会发出 FedCM 调用,以便使用身份提供方 idp.example
登录用户:
// The user will be asked to grant FedCM permission.
const cred = await navigator.credentials.get({
identity: {
providers: [{
configURL: 'https://idp.example/fedcm.json',
clientId: '123',
}],
},
});
用户登录后,IdP 可以从 idp.example
iframe 中调用 requestStorageAccess()
,前提是 RP 已通过权限政策明确允许这样做。嵌入内容将自动获得对其自身顶级 Cookie 的存储权限,而无需用户激活或再次显示权限提示:
// Make this call within the embedded IdP iframe:
// No user gesture is needed, and the storage access will be auto-granted.
await document.requestStorageAccess();
// This returns `true`.
const hasAccess = await document.hasStorageAccess();
只有当用户使用 FedCM 登录时,系统才会自动授予此权限。身份验证处于非活动状态后,授予存储空间访问权限时将适用标准的 SAA 要求。
使用存储空间访问标头进行后续加载
存储空间访问权限标头是一种更高效的推荐方法,可用于启用嵌入内容(包括非 iframe 资源)的加载。此功能从 Chrome 133 开始提供。借助存储空间访问权限标头,浏览器可以识别用户何时已在当前情境中向第三方来源授予 storage-access
权限,并在后续访问期间加载有权访问未分区的 Cookie 的资源。
存储空间访问标头流程
使用存储空间访问权限标头后,后续的网页访问将触发以下流程:
- 用户之前访问过嵌入
calendar.example
资源的website.example
,并通过document.requestStorageAccess()
调用授予了storage-access
权限。 - 用户再次访问嵌入了
calendar.example
资源的website.example
。与之前一样,此请求尚无权访问 Cookie。不过,用户之前已授予storage-access
权限,并且提取操作包含Sec-Fetch-Storage-Access: inactive
标头,表示未分区的 Cookie 访问权限可用但未启用。 calendar.example
服务器会使用Activate-Storage-Access: retry; allowed-origin='<origin>'
标头(在本例中,<origin>
为https://website.example
)进行响应,以指示资源提取需要使用具有storage-access
权限的分区 Cookie。- 浏览器会重试请求,这次会包含未分区的 Cookie(为此提取和后续提取启用
storage-access
权限)。 calendar.example
服务器会返回个性化 iframe 内容。响应包含Activate-Storage-Access: load
标头,用于指示浏览器应在启用storage-access
权限的情况下加载内容(换句话说,使用未分区的 Cookie 访问权限加载,就像调用了document.requestStorageAccess()
一样)。- 用户代理使用
storage-access
权限加载具有未分区的 Cookie 访问权限的 iframe 内容。完成此步骤后,该微件便可按预期运行。
使用存储空间访问权限标头
下表列出了存储空间访问权限标头。
流程 | 标题 | 值 | 说明 |
---|---|---|---|
请求 |
Sec-Fetch-Storage-Access 注意:浏览器会在包含凭据(例如 new Request('request.example', { credentials: 'include' }); )的跨站请求中自动发送此标头。
|
none |
嵌入内容没有存储空间访问权限。 |
inactive |
嵌入有权限,但未使用该权限。 该请求还必须包含 Origin 标头。
|
||
active |
嵌入有未分区的 Cookie 访问权限。 | ||
响应 | Activate-Storage-Access |
load |
指示浏览器向嵌入程序授予对请求资源的未分区 Cookie 的访问权限。 如果已授予 storage-access 权限,则添加此标头等同于调用 document.requestStorageAccess() 。这意味着,系统不会向用户显示任何其他提示。
|
retry |
指示浏览器激活存储空间访问权限,然后重试请求。 | ||
allowed-origin |
<origin> |
指定哪些来源可以发起使用凭据的请求(例如https://site.example 或 * )。 |
例如,存储空间访问权限标头可用于从第三方加载图片:
// On the client side
<img src="https://server.example/image">
在这种情况下,server.example
应在服务器端实现以下逻辑:
app.get('/image', (req, res) => {
const storageAccessHeader = req.headers['sec-fetch-storage-access'];
if (storageAccessHeader === 'inactive') {
// The user needs to grant permission, trigger a prompt
// Check if the requesting origin is allowed
// to send credentialed requests to this server.
// Assuming the `validate_origin(origin)` method is previously defined:
if (!validate_origin(req.headers.origin)) {
res.status(401).send(req.headers.origin +
' is not allowed to send credentialed requests to this server.');
return;
}
// 'retry' header value indicates that the content load request should be re-sent after the user has granted permissions
res.set('Activate-Storage-Access', `retry; allowed-origin='${req.headers.origin}'`);
res.status(401).send('This resource requires storage access. Please grant permission.');
} else if (storageAccessHeader === 'active') {
// User has granted permission, proceed with access
res.set('Activate-Storage-Access', 'load');
// Include the actual iframe content here
res.send('This is the content that requires cookie access.');
} else {
// Handle other cases (e.g., 'Sec-Fetch-Storage-Access': 'none')
}
});
Storage Access API 演示版使用 Storage Access Header 嵌入第三方内容(包括非 iframe 图片)。
使用 storage-access
权限查询
如需检查是否可以在无需用户互动的情况下授予访问权限,您可以检查 storage-access
权限的状态,并仅在无需用户操作时提前进行 requestStoreAccess()
调用,而不是在需要用户互动时进行调用并导致其失败。
这样一来,您或许还可以通过显示其他内容(例如登录按钮)来提前处理提示需求。
以下代码将 storage-access
权限检查添加到前面的示例中:
// Set a hasAccess boolean variable which defaults to false except for
// browsers which don't support the API - where we assume
// such browsers also don't block third-party cookies.
let hasAccess = false;
async function hasCookieAccess() {
// Check if Storage Access API is supported
if (!document.requestStorageAccess) {
// Storage Access API is not supported so best we can do is
// hope it's an older browser that doesn't block 3P cookies.
return true;
}
// Check if access has already been granted
if (await document.hasStorageAccess()) {
return true;
}
// Check the storage-access permission
// Wrap this in a try/catch for browsers that support the
// Storage Access API but not this permission check
// (e.g. Safari and earlier versions of Firefox).
let permission;
try {
permission = await navigator.permissions.query(
{name: 'storage-access'}
);
} catch (error) {
// storage-access permission not supported. Assume no cookie access.
return false;
}
if (permission) {
if (permission.state === 'granted') {
// Permission has previously been granted so can just call
// requestStorageAccess() without a user interaction and
// it will resolve automatically.
try {
await document.requestStorageAccess();
return true;
} catch (error) {
// This shouldn't really fail if access is granted, but return false
// if it does.
return false;
}
} else if (permission.state === 'prompt') {
// Need to call requestStorageAccess() after a user interaction
// (potentially with a prompt). Can't do anything further here,
// so handle this in the click handler.
return false;
} else if (permission.state === 'denied') {
// Not used: see https://github.com/privacycg/storage-access/issues/149
return false;
}
}
// By default return false, though should really be caught by earlier tests.
return false;
}
async function handleCookieAccessInit() {
hasAccess = await hasCookieAccess();
if (hasAccess) {
// Use the cookies.
}
}
handleCookieAccessInit();
沙盒化 iframe
在沙盒化 iframe 中使用 Storage Access API 时,需要具有以下沙盒权限:
- 必须使用
allow-storage-access-by-user-activation
才能访问 Storage Access API。 - 必须允许使用 JavaScript 调用 API,否则
allow-scripts
将不允许。 - 必须使用
allow-same-origin
才能访问同源 Cookie 和其他存储空间。
例如:
<iframe sandbox="allow-storage-access-by-user-activation
allow-scripts
allow-same-origin"
src="..."></iframe>
Cookie 要求
若要在 Chrome 中使用 Storage Access API 访问跨网站 Cookie,必须使用以下两个属性设置跨网站 Cookie:
SameSite=None
- 必须将 Cookie 标记为跨网站 CookieSecure
- 可确保仅访问由 HTTPS 网站设置的 Cookie。
在 Firefox 和 Safari 中,Cookie 默认为 SameSite=None
,并且它们不会将 SAA 限制为 Secure
Cookie,因此这些属性不是必需的。建议明确指定 SameSite
属性,并始终使用 Secure
Cookie。
顶级页面访问权限
Storage Access API 旨在让您能够访问嵌入的 iframe 中的第三方 Cookie。
顶级网页需要访问第三方 Cookie 的其他用例也很多。例如,受 Cookie 限制的图片或脚本,网站所有者可能希望将其直接包含在顶级文档中,而不是在 iframe 中。为了解决此用例,Chrome 提出了对 Storage Access API 的扩展,其中添加了 requestStorageAccessFor()
方法。
requestStorageAccessFor()
方法
requestStorageAccessFor()
方法的运作方式与 requestStorageAccess()
类似,但适用于顶级资源。此功能只能用于相关网站集中的网站,以防止向第三方 Cookie 授予一般访问权限。
如需详细了解如何使用 requestStorageAccessFor()
,请参阅 Related Website Set:开发者指南。
top-level-storage-access
权限查询
Browser Support
与 storage-access
权限类似,top-level-storage-access
权限用于检查是否可以为 requestStorageAccessFor()
授予访问权限。
与 RWS 搭配使用时,Storage Access API 有何不同?
将“相关网站集”与 Storage Access API 搭配使用时,可以使用下表中详述的某些额外功能:
不使用 RWS | 使用 RWS | |
---|---|---|
需要用户手势才能发起存储空间访问权限请求 | ||
要求用户先在顶级上下文中访问请求的存储源,然后才能授予访问权限 | ||
可以跳过首次使用时的用户提示 | ||
如果之前已授予访问权限,则无需调用 requestStorageAccess |
||
自动授予对关联网站网站中其他网域的访问权限 | ||
支持 requestStorageAccessFor 以获取顶级页面访问权限 |
演示:设置和访问 Cookie
以下演示展示了如何在演示的第二个网站的嵌入式框架中访问您在演示的第一个屏幕中设置的 Cookie:
storage-access-api-demo.glitch.me
此演示需要使用停用了第三方 Cookie 的浏览器:
- 已设置
chrome://flags/#test-third-party-cookie-phaseout
标志并重启浏览器的 Chrome 118 或更高版本。 - Firefox
- Safari
演示:设置本地存储
以下演示展示了如何使用 Storage Access API 从第三方 iframe 访问未分区的广播通道:
https://saa-beyond-cookies.glitch.me/
此演示需要 Chrome 125 或更高版本,且已启用 test-third-party-cookie-phaseout 标志。