使用 Storage Access API
该 Storage Access API 可由嵌入式跨站文档用于验证其是否具有访问 第三方 Cookie 和 未分区状态 的权限,如果没有,则可用于请求访问。我们将简要介绍一个常见的存储访问场景。
注意:当我们在 Storage Access API 的内容中谈论第三方 Cookie 时,我们隐式地指的是 未分区 的第三方 Cookie。
用法说明
Storage Access API 的设计目的是允许嵌入式内容请求访问第三方 Cookie 和未分区状态——大多数现代浏览器默认会阻止此类访问以保护用户隐私。由于嵌入式内容不知道浏览器在这方面的行为将如何,因此在尝试读取或写入 Cookie 之前,最好始终检查嵌入的 <iframe> 是否具有存储访问权限。这对于 Document.cookie 访问尤其如此,因为当第三方 Cookie 访问被阻止时,浏览器通常会返回一个空的 Cookie 罐。
在下面的示例中,我们展示了嵌入式跨站 <iframe> 如何在浏览器存储访问策略下访问第三方 Cookie 和未分区状态,而该策略否则会阻止对其的访问。
允许沙盒化的 <iframe> 使用该 API
首先,如果 <iframe> 被沙盒化,嵌入网站需要添加 allow-storage-access-by-user-activation 沙盒令牌,以便 Storage Access API 请求能够成功,同时还需要 allow-scripts 和 allow-same-origin 令牌,以便它能够执行脚本来调用 API 并在具有 Cookie 和状态的源中执行它。
<iframe
sandbox="allow-storage-access-by-user-activation
allow-scripts
allow-same-origin">
…
</iframe>
检查和请求存储访问
现在来看在嵌入式文档内部执行的代码。在这段代码中
- 我们首先使用功能检测(
if (document.hasStorageAccess) {})来检查 API 是否受支持。如果不支持,我们就运行访问 Cookie 的代码,并希望它能正常工作。无论如何,它都应该以防御性方式编码来应对这种情况。 - 如果 API 受支持,我们调用
document.hasStorageAccess()。 - 如果该调用返回
true,则表示此<iframe>已获得访问权限,我们可以立即运行访问 Cookie 和状态的代码。 - 如果该调用返回
false,我们则调用Permissions.query()来检查是否已授予访问第三方 Cookie 和未分区状态的权限(即,是否授予了给另一个同站嵌入)。我们将整个部分包装在一个try...catch块中,因为 某些浏览器不支持"storage-access"权限,这可能导致query()调用抛出错误。如果抛出错误,我们会将其报告给控制台,然后尝试运行 Cookie 代码。 - 如果权限状态为
"granted",我们立即调用document.requestStorageAccess()。此调用将自动解析,为用户节省一些时间,然后我们可以运行访问 Cookie 和状态的代码。 - 如果权限状态为
"prompt",我们在用户交互后调用document.requestStorageAccess()。此调用可能会触发用户提示。如果此调用解析,那么我们就可以运行访问 Cookie 和状态的代码。 - 如果权限状态为
"denied",则表示用户已拒绝我们访问第三方 Cookie 或未分区状态的请求,我们的代码将无法使用它们。
function doThingsWithCookies() {
document.cookie = "foo=bar"; // set a cookie
}
function doThingsWithLocalStorage(handle) {
handle.localStorage.setItem("foo", "bar"); // set a local storage key
}
async function handleCookieAccess() {
if (!document.hasStorageAccess) {
// This browser doesn't support the Storage Access API
// so let's just hope we have access!
doThingsWithCookies();
} else {
const hasAccess = await document.hasStorageAccess();
if (hasAccess) {
// We have access to third-party cookies, so let's go
doThingsWithCookies();
// If we want to modify unpartitioned state, we need to request a handle.
const handle = await document.requestStorageAccess({
localStorage: true,
});
doThingsWithLocalStorage(handle);
} else {
// Check whether third-party cookie access has been granted
// to another same-site embed
try {
const permission = await navigator.permissions.query({
name: "storage-access",
});
if (permission.state === "granted") {
// If so, you can just call requestStorageAccess() without a user interaction,
// and it will resolve automatically.
const handle = await document.requestStorageAccess({
cookies: true,
localStorage: true,
});
doThingsWithLocalStorage(handle);
doThingsWithCookies();
} else if (permission.state === "prompt") {
// Need to call requestStorageAccess() after a user interaction
btn.addEventListener("click", async () => {
try {
const handle = await document.requestStorageAccess({
cookies: true,
localStorage: true,
});
doThingsWithLocalStorage(handle);
doThingsWithCookies();
} catch (err) {
// If there is an error obtaining storage access.
console.error(`Error obtaining storage access: ${err}.
Please sign in.`);
}
});
} else if (permission.state === "denied") {
// User has denied third-party cookie access, so we'll
// need to do something else
}
} catch (error) {
console.log(`Could not access permission state. Error: ${error}`);
doThingsWithCookies(); // Again, we'll have to hope we have access!
}
}
}
}
注意:除非嵌入式内容当前正在处理用户手势(例如点击),否则 requestStorageAccess() 请求将自动被拒绝(瞬时激活),或者如果之前已授予权限。如果之前未授予权限,则必须在基于用户手势的事件处理程序中运行 requestStorageAccess() 请求,如上所示。
相关网站集
仅限 Chrome 的 相关网站集 功能可以被视为一种渐进增强机制,它与 Storage Access API 一起工作——支持的浏览器将自动授予同一集内网站之间的第三方 Cookie 和未分区状态访问权限。这意味着无需经过上面描述的 usual 用户权限提示流程,对该集内网站的用户来说,可以获得更友好的体验。
代表嵌入式资源向顶级站点请求存储访问
上述 Storage Access API 功能允许嵌入式文档请求其自身的第三方 Cookie 访问权限。还有一个额外的实验性方法可用,Document.requestStorageAccessFor(),这是 Storage Access API 的一项提议扩展,它允许顶级站点代表特定的相关源请求存储访问。
requestStorageAccessFor() 方法解决了在那些使用需要 Cookie 的跨站图片或脚本的顶级站点上采用 Storage Access API 所面临的挑战。它可以为直接嵌入到顶级站点中的、无法自行请求存储访问的跨站资源启用第三方 Cookie 访问,例如通过 <img> 或 <script> 元素。
要使 requestStorageAccessFor() 生效,调用它的顶级页面和它请求存储访问的嵌入式资源都需要是同一 相关网站集 的一部分。
requestStorageAccessFor() 的典型用法如下(这次使用常规的 Promise 风格而不是 async/await)
navigator.permissions
.query({
name: "top-level-storage-access",
requestedOrigin: "https://example.com",
})
.then((permission) => {
if (permission.state === "granted") {
// Permission has already been granted
// No need to call requestStorageAccessFor() again, just start using cookies
doThingsWithCookies();
} else if (permission.state === "prompt") {
// Need to call requestStorageAccessFor() after a user interaction
btn.addEventListener("click", () => {
// Request storage access
rSAFor();
});
} else if (permission.state === "denied") {
// User has denied third-party cookie access, so we'll
// need to do something else
}
});
function rSAFor() {
if ("requestStorageAccessFor" in document) {
document.requestStorageAccessFor("https://example.com").then(
(res) => {
doThingsWithCookies();
},
(err) => {
// Handle errors
},
);
}
}
注意:与 requestStorageAccess() 不同,当调用 requestStorageAccessFor() 时,Chrome 不会检查最近 30 天内顶级文档的交互情况,因为用户已经在页面上了。有关此行为的更多详细信息,请参阅 浏览器特定差异 > Chrome。
当查询代表其他源发出的存储访问请求的权限状态时,使用的权限名称与 Storage Access API 的其他部分不同:是 "top-level-storage-access" 而不是 "storage-access"。在上面的代码中,我们使用了以下调用
navigator.permissions.query({
name: "top-level-storage-access",
requestedOrigin: "https://example.com",
});
来发现该源是否已预先获得权限,或者是否仍需要请求 Cookie 访问。
- 如果权限状态为
"granted",我们就可以开始使用 Cookie 了;requestStorageAccessFor()已经被调用,所以不需要再次调用它。 - 如果权限状态为
"prompt",我们需要在用户手势(例如按钮点击)内调用document.requestStorageAccessFor("https://example.com")。
在 "top-level-storage-access" 权限被授予后,跨站请求将包含 Cookie(如果它们包含 CORS / crossorigin),因此站点可能希望在触发请求之前等待。此类请求必须使用 credentials: "include" 选项,并且资源必须包含 crossorigin="use-credentials" 属性。
例如
function checkCookie() {
fetch("https://example.com/getcookies.json", {
method: "GET",
credentials: "include",
})
.then((response) => response.json())
.then((json) => {
// Do something
});
}