1. 12 网页存储
    1. 12.1 简介
    2. 12.2 API
      1. 12.2.1 Storage 接口
      2. 12.2.2 sessionStorage 获取器
      3. 12.2.3 localStorage 获取器
      4. 12.2.4 StorageEvent 接口
    3. 12.3 隐私
      1. 12.3.1 用户追踪
      2. 12.3.2 数据敏感性
    4. 12.4 安全
      1. 12.4.1 DNS 欺骗攻击
      2. 12.4.2 跨目录攻击
      3. 12.4.3 实现风险

12 网页存储

Web_Storage_API

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

Web_Storage_API/Using_the_Web_Storage_API

12.1 简介

本节是非规范性的。

本规范引入了两种相关机制,类似于 HTTP 会话 Cookie,用于在客户端存储键值对。 [COOKIES]

第一个机制用于用户执行单个交易的场景,但用户可能在不同的窗口同时执行多个交易。

Cookie 无法很好地处理这种情况。例如,用户可能在两个不同的窗口中购买机票,使用同一个网站。如果网站使用 Cookie 来跟踪用户正在购买的机票,那么当用户在两个窗口中点击页面时,当前正在购买的机票会从一个窗口“泄露”到另一个窗口,这可能会导致用户购买两张同一个航班的机票而没有注意到。

为了解决这个问题,本规范引入了 sessionStorage 获取器。网站可以将数据添加到会话存储中,该数据可在同一网站在该窗口中打开的任何页面中访问。

例如,页面可以有一个复选框,用户勾选该复选框表示他们想要购买保险

<label>
 <input type="checkbox" onchange="sessionStorage.insurance = checked ? 'true' : ''">
  I want insurance on this trip.
</label>

然后,后续页面可以通过脚本检查用户是否勾选了复选框

if (sessionStorage.insurance) { ... }

如果用户在网站上打开了多个窗口,每个窗口都会拥有其独立的会话存储对象副本。

第二个存储机制旨在实现跨多个窗口的存储,并在当前会话结束后继续存在。特别是,网络应用程序可能希望出于性能原因,将兆字节的用户数据(例如,整个用户创作的文档或用户的邮箱)存储在客户端。

同样,Cookie 无法很好地处理这种情况,因为它们会与每个请求一起传输。

localStorage 获取器用于访问页面的本地存储区域。

example.com 网站可以通过将其页面底部添加以下代码来显示用户加载其页面的次数

<p>
  You have viewed this page
  <span id="count">an untold number of</span>
  time(s).
</p>
<script>
  if (!localStorage.pageLoadCount)
    localStorage.pageLoadCount = 0;
  localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
  document.getElementById('count').textContent = localStorage.pageLoadCount;
</script>

每个网站都有其独立的存储区域。

localStorage 获取器提供对共享状态的访问。本规范没有定义与多进程用户代理中的其他代理集群的交互,作者应假设没有锁定机制。例如,网站可以尝试读取键的值,增加其值,然后使用新值写入该值,并将新值用作会话的唯一标识符;如果网站在两个不同的浏览器窗口中同时执行此操作两次,它可能会使用相同的“唯一”标识符来标识这两个会话,这可能会产生灾难性的后果。

12.2 API

存储

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

12.2.1 Storage 接口

[Exposed=Window]
interface Storage {
  readonly attribute unsigned long length;
  DOMString? key(unsigned long index);
  getter DOMString? getItem(DOMString key);
  setter undefined setItem(DOMString key, DOMString value);
  deleter undefined removeItem(DOMString key);
  undefined clear();
};
storage.length

Storage/length

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

返回键值对的数量。

storage.key (n)

Storage/key

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

返回第 n 个键的名称,如果 n 大于或等于键值对的数量,则返回 null。

value = storage.getItem (key)

Storage/getItem

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+
value = storage[key]

返回与给定 key 关联的当前值,如果给定 key 不存在,则返回 null。

storage.setItem (key, value)

Storage/setItem

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+
storage[key] = value

将由 key 标识的对的值设置为 value,如果以前不存在与 key 关联的键值对,则创建一个新的键值对。

如果无法设置新值,则抛出 "QuotaExceededError" DOMException 异常。(设置可能会失败,例如,如果用户已为该网站禁用了存储,或者如果已超过配额。)

在持有等效 Storage 对象的 Window 对象上分派 storage 事件。

storage.removeItem (key)

Storage/removeItem

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+
delete storage[key]

如果存在具有给定 key 的键值对,则删除具有给定 key 的键值对。

在持有等效 Storage 对象的 Window 对象上分派 storage 事件。

storage.clear()

Storage/clear

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android6+Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

如果存在任何键值对,则删除所有键值对。

在持有等效 Storage 对象的 Window 对象上分派 storage 事件。

Storage 对象有一个关联的

映射
一个 存储代理映射
类型
"local" 或 "session"。

重新排序 Storage 对象 storage,请以 实现定义的方式重新排序 storage映射条目

不幸的是,迭代顺序没有定义,并且在大多数变异后会发生变化。

广播 Storage 对象 storage,在给定 keyoldValuenewValue 的情况下,执行以下步骤

  1. thisDocumentstorage相关全局对象关联的 Document

  2. urlthisDocumentURL

  3. remoteStorages 为所有 Storage 对象,不包括 storage,其

    并且,如果 类型 为 "session",其 相关设置对象关联的 Document节点可导航可遍历可导航thisDocument节点可导航可遍历可导航

  4. 对于每个 remoteStorageremoteStorages:在给定 remoteStorage相关全局对象DOM 操作任务源排队一个全局任务,以 remoteStorage相关全局对象 上触发名为 storage 的事件,使用 StorageEvent,其中 key 初始化为 keyoldValue 初始化为 oldValuenewValue 初始化为 newValueurl 初始化为 urlstorageArea 初始化为 remoteStorage

    与生成的任务相关的文档对象不一定完全激活,但在此类对象上触发的事件会被事件循环忽略,直到文档再次完全激活


The length getter steps are to return this's map's size.

The key(index) method steps are

  1. 如果 index 大于或等于 this's map's size,则返回 null。

  2. keys 为在 this's map 上运行获取键 的结果。

  3. 返回 keys[index]。

Storage 对象 storage支持的属性名称 是在 storage's map 上运行 获取键 的结果。

The getItem(key) method steps are

  1. 如果 this's map[key] 不存在,则返回 null。

  2. 返回 this's map[key]。

The setItem(key, value) method are

  1. oldValue 为 null。

  2. reorder 为 true。

  3. 如果 this's map[key] 存在

    1. oldValue 设置为 this's map[key]。

    2. 如果 oldValue value,则返回。

    3. reorder 设置为 false。

  4. 如果无法存储 value,则抛出"QuotaExceededError" DOMException 异常。

  5. 设置 this's map[key] 为 value

  6. 如果 reorder 为 true,则重新排序 this

  7. 广播 this,使用 keyoldValuevalue

The removeItem(key) method steps are

  1. 如果 this's map[key] 不存在,则返回 null。

  2. oldValue 设置为 this's map[key]。

  3. 删除 this's map[key]。

  4. 重新排序 this

  5. 广播 this,使用 keyoldValue 和 null。

The clear() method steps are

  1. 清除 this's map

  2. 广播 this,使用 null、null 和 null。

12.2.2 The sessionStorage getter

interface mixin WindowSessionStorage {
  readonly attribute Storage sessionStorage;
};
Window includes WindowSessionStorage;
window.sessionStorage

Window/sessionStorage

所有当前引擎都支持。

Firefox2+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

返回与该 window 的来源的会话存储区域关联的Storage 对象。

如果文档来源不透明来源,或者请求违反了策略决定(例如,如果用户代理配置为不允许页面持久化数据),则抛出 "SecurityError" DOMException

文档 对象有一个关联的会话存储持有者,它可以是 null 或Storage 对象。它最初为 null。

(This is a tracking vector.) The sessionStorage getter steps are

  1. 如果 this's 关联的 Document's 会话存储持有者 不为 null,则返回 this's 关联的 Document's 会话存储持有者

  2. map 为使用 this's 相关设置对象 和 "sessionStorage" 运行 获取会话存储瓶地图 的结果。

  3. 如果 map 为失败,则抛出 "SecurityError" DOMException

  4. storage 为一个新的Storage 对象,其 mapmap

  5. this's 关联的 Document's 会话存储持有者 设置为 storage

  6. 返回 storage

创建新的辅助浏览上下文和文档 之后,会话存储被复制 过去。

12.2.3 The localStorage getter

interface mixin WindowLocalStorage {
  readonly attribute Storage localStorage;
};
Window includes WindowLocalStorage;
window.localStorage

Window/localStorage

所有当前引擎都支持。

Firefox3.5+Safari4+Chrome4+
Opera10.5+Edge79+
Edge (传统)12+Internet Explorer8+
Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11+

返回与 window 的来源的本地存储区域关联的Storage 对象。

如果文档来源不透明来源,或者请求违反了策略决定(例如,如果用户代理配置为不允许页面持久化数据),则抛出 "SecurityError" DOMException

文档 对象有一个关联的本地存储持有者,它可以是 null 或Storage 对象。它最初为 null。

(This is a tracking vector.) The localStorage getter steps are

  1. 如果 this's 关联的 Document's 本地存储持有者 不为 null,则返回 this's 关联的 Document's 本地存储持有者

  2. map 为使用 this's 相关设置对象 和 "localStorage" 运行 获取本地存储瓶地图 的结果。

  3. 如果 map 为失败,则抛出 "SecurityError" DOMException

  4. storage 为一个新的Storage 对象,其 mapmap

  5. this's 关联的 Document's 本地存储持有者 设置为 storage

  6. 返回 storage

12.2.4 StorageEvent 接口

StorageEvent

所有当前引擎都支持。

Firefox13+Safari4+Chrome1+
Opera12.1+Edge79+
Edge (Legacy)12+Internet Explorer9+
Firefox Android?Safari iOS3+Chrome Android?WebView Android37+Samsung Internet?Opera Android12.1+
[Exposed=Window]
interface StorageEvent : Event {
  constructor(DOMString type, optional StorageEventInit eventInitDict = {});

  readonly attribute DOMString? key;
  readonly attribute DOMString? oldValue;
  readonly attribute DOMString? newValue;
  readonly attribute USVString url;
  readonly attribute Storage? storageArea;

  undefined initStorageEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional DOMString? key = null, optional DOMString? oldValue = null, optional DOMString? newValue = null, optional USVString url = "", optional Storage? storageArea = null);
};

dictionary StorageEventInit : EventInit {
  DOMString? key = null;
  DOMString? oldValue = null;
  DOMString? newValue = null;
  USVString url = "";
  Storage? storageArea = null;
};
event.key

返回正在更改的存储项的键。

event.oldValue

返回正在更改其值的存储项的键的旧值。

event.newValue

返回正在更改其值的存储项的键的新值。

event.url

返回发生存储项更改的文档的URL

event.storageArea

返回受影响的Storage 对象。

keyoldValuenewValueurlstorageArea 属性必须返回它们被初始化时的值。

initStorageEvent(type, bubbles, cancelable, key, oldValue, newValue, url, storageArea) 方法必须以类似于同名initEvent() 方法的方式初始化事件。 [DOM]

12.3 隐私

12.3.1 用户追踪

第三方广告商(或任何能够将内容分发到多个网站的实体)可以使用存储在其本地存储区域中的唯一标识符跨多个会话跟踪用户,构建用户兴趣概况,以便进行高度针对性的广告。结合了解用户真实身份的网站(例如需要身份验证凭据的电子商务网站),这可以让压制性团体比在一个纯粹匿名网络使用环境中更准确地定位个人。

有许多技术可以用来降低用户追踪的风险。

阻止第三方存储

用户代理可以将对localStorage 对象的访问限制为源自活动文档域名的脚本顶级可遍历,例如,拒绝对在iframe 中运行的其他域名的页面上的 API 的访问。

存储数据过期

用户代理可以(可能以用户配置的方式)在一段时间后自动删除存储的数据。

例如,用户代理可以配置为将第三方本地存储区域视为仅限会话的存储,在用户关闭所有可以访问它的可导航时删除数据。

这可以限制网站跟踪用户的可能性,因为网站只能在用户自己向网站进行身份验证时(例如,通过购买商品或登录服务)才能跨多个会话跟踪用户。

然而,这也降低了 API 作为长期存储机制的用处。如果用户不完全了解数据过期带来的影响,它也会将用户的安全置于风险之中。

将持久存储视为 Cookie

如果用户试图通过清除 Cookie 而没有清除本地存储区域中存储的数据来保护他们的隐私,那么网站可以通过使用这两个功能作为彼此的冗余备份来阻止这些尝试。用户代理应该以帮助用户了解这种可能性并使他们能够同时删除所有持久存储功能中的数据的方式来呈现清除这些功能的界面。 [COOKIES]

特定网站对访问本地存储区域的白名单

用户代理可以允许网站以不受限制的方式访问会话存储区域,但要求用户授权访问本地存储区域。

存储数据的来源追踪

用户代理可以记录包含来自导致数据存储的第三方来源的内容的网站的来源

如果然后使用此信息来显示当前位于持久存储中的数据的视图,它将允许用户就需要修剪持久存储的哪些部分做出明智的决定。结合黑名单(“删除此数据并阻止此域名再次存储数据”),用户可以将持久存储的使用限制在他们信任的网站。

共享黑名单

用户代理可以允许用户共享他们的持久存储域黑名单。

这将允许社区共同行动来保护他们的隐私。

虽然这些建议可以防止这个 API 被简单地用于用户追踪,但它们并不能完全阻止它。在一个域名内,网站可以在一个会话中继续跟踪用户,然后将所有这些信息以及网站获得的任何识别信息(姓名、信用卡号码、地址)传递给第三方。如果第三方与多个网站合作以获取此类信息,则仍然可以创建用户概况。

然而,即使没有任何来自用户代理的合作,用户追踪在某种程度上也是可能的,例如通过在 URL 中使用会话标识符,这是一种已经普遍用于无害目的但很容易被重新用于用户追踪的技术(即使是追溯性地)。然后,可以使用访问者的 IP 地址和其他用户特定数据(例如用户代理标头和配置设置)将这些信息与其他网站共享,将独立的会话合并到连贯的用户概况中。

12.3.2 数据敏感性

用户代理应该将持久存储的数据视为可能敏感的;电子邮件、日历约会、健康记录或其他机密文件很可能存储在这个机制中。

为此,用户代理应该确保在删除数据时,立即将其从底层存储中删除。

12.4 安全

12.4.1 DNS 欺骗攻击

由于 DNS 欺骗攻击的可能性,不能保证声称位于特定域名的主机确实来自该域名。为了减轻这种风险,页面可以使用 TLS。使用 TLS 的页面可以确保只有用户、代表用户工作的软件以及使用 TLS 并具有识别它们来自相同域名的证书的其他页面可以访问其存储区域。

12.4.2 跨目录攻击

不同的作者共享一个主机名,例如在现已停用的geocities.com 上托管内容的用户,他们共享一个本地存储对象。没有功能可以限制路径名によるアクセス。因此,共享主机上的作者应避免使用这些功能,因为其他作者可以轻松地读取数据并覆盖数据。

即使提供了路径限制功能,通常的 DOM 脚本安全模型也会使绕过此保护并从任何路径访问数据变得微不足道。

12.4.3 实现风险

在实现这些持久存储功能时,两个主要风险是让恶意网站读取来自其他域名的信息,以及让恶意网站写入然后从其他域名读取的信息。

让第三方网站读取不应该从其域名读取的数据会导致信息泄漏。例如,一个用户在一个域名上的购物愿望清单可以被另一个域名用于针对性广告;或者用户在一个文字处理网站上存储的正在进行的机密文档可以被竞争公司的网站检查。

让第三方网站将数据写入其他域名的持久存储中会导致信息欺骗,这同样危险。例如,一个恶意网站可以将商品添加到用户的愿望清单中;或者一个恶意网站可以将用户的会话标识符设置为已知 ID,恶意网站然后可以使用该 ID 跟踪用户在受害者网站上的操作。

因此,严格遵循本规范中描述的来源模型对于用户安全至关重要。