使用 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
});
}