超越 Cookie:当今的客户端数据存储

当 cookie 被首次引入时,它是浏览器保存数据的唯一方式。之后又有了很多新的选择:Web Storage API、IndexedDB 和 Cache API。那么 cookie 死了吗?我们来看看这些在浏览器中存储数据的技术。

Cookies

Cookie 是由服务器发送或在客户端上设置的信息单位,保存在用户的本地浏览器上。它们会自动附加到每个请求上。由于 HTTP 是无状态协议,因此 cookie 允许将信息存储在客户端上,以便将其他上下文数据传给该服务器。

Cookie 有一些标志,对于提高数据的安全性非常有用。 HttpOnly 标志阻止用 JavaScript 访问 cookie 的行为,只有附加在 HTTP 请求上时才能访问它们。这非常适合防止通过 XSS(跨站点脚本)攻击造成数据泄露。

此外,Secure 标志确保仅在通过 HTTPS 协议发送请求时才发送 cookie。 SameSite 标志,可以设置为 laxstrict(它们的差异看这里),可用于帮助防止 CSRF(跨站点请求伪造)请求。它告诉浏览器只有在请求是与请求者在同一域中的 URL 时才发送 cookie。

什么时候使用 cookies?

那么,在哪些情况下你希望获得 Cookie?最常见的应用场景之一是授权 token 。由于 HttpOnly 标志为 XSS 攻击添加了额外的保护层,SameSite 可以防止 CSRF,而 Secure 可以确保你的 cookie 被加密,这使你的身份验证token 有额外的保护层。

由于 auth token 非常小,因此你无需担心请求过大。此外由于它们会自动附加到每个请求,因此使用 cookie 可以在服务器上确定用户是否经过身份验证。这对于服务器呈现的内容非常有用,例如你希望将未经过身份验证的用户重定向到登录页面。

Cookie 的另一个用途是存储用户的语言代码。由于你可能希望在大多数请求中访问用户的语言,因此你可以利用它自动附加。

如何使用 cookies?

前面经讨论了要使用 cookie 的原因,现在来看看你可以如何使用 cookie。要从服务器上给客户端设置 cookie,需要在 HTTP 响应中添加 Set-Cookie 标头。 Cookie 应采用 key=value 的格式。如果你要在 Node.js 程序中设置 cookie,你的代码可能像下面这样:

response.setHeader('Set-Cookie', ['user_lang=en-us', 'user_theme=dark_mode']);

这将会设置两个 cookie:它将 user_lang 设置为 en-us,将 user_theme 设置为 dark_mode

Cookie 也可以由客户端操纵。要设置 cookie,可以用 key=value 的格式为 document.cookie 赋值。如果 key 已存在,则会被覆盖掉。

document.cookie = 'user_lang=es-es';

如果已经定义了 user_lang,它现在等于es-es

你可以通过访问 document.cookie 值来查看所有的 cookie。这将返回一串以分号做分隔的键值对。

document.cookie = 'user_lang=en-us';
document.cookie = 'user_theme=light_mode';
console.log(document.cookie); // 'user_lang=en-us; user_theme=light_mode;'

要增加键值对的可访问性,可以使用以下函数将此字符串解析为对象:

const parseCookies = x => x
  .split(';')
  .map(e => e.trim().split('='))
  .reduce((obj, [key, value]) => ({...obj, [key]: value}), {});

If you need to set one of the flags onto your cookie, you can add them after a semicolon. For example, if you’d like to set the Secure and SameSite flags onto your cookie, you would do the following:

如果你需要将其中一个标志设置到 cookie 上,可以在分号后添加它们。例如你想在 Cookie 上设置 SecureSameSite 标志,则可以执行以下操作:

document.cookie = 'product_ids=123,321;secure;samesite=lax'

由于 HTTPOnly 的作用是使 cookie 只能在服务器上访问,因此它只能由服务器添加。

除了这些安全标志之外,你还可以设置 Max-Age( cookie 应该保存的秒数)或 Expires(Cookie应该过期的日期)。如果这些都未设置,则 cookie 将跟随浏览器会话的持续时间。如果用户使用隐身模式,则会在用户会话关闭时删除 Cookie。

由于处理 cookie 的接口不是很友好,所以你可以使用诸如 js-cookie 之类的库来方便对其的操作。

Web Storage API

Web Storage API 是一种在本地存储数据的新选项。它在 HTML5 中中添加,Web Storage API 包括localStoragesessionStorage。虽然 cookie 通常处理 server/client 通信,但 Web Storage API 最适用于保存客户端数据。

我们已经将 cookie 作为在本地存储数据的选项,为什么还需要 Web 存储?其中一个原因是:由于 cookie 会自动添加到每个 HTTP 请求中,因此请求大小会变得臃肿。所以你可以用 Web Storage API 存储比 cookie 更大量的数据。

另一个优点是更直观的 API。如果使用 cookie,你需要手动解析 cookie 字符串来访问各个键。 Web Storage 使这更加容易。如果要设置或获取值,可以使用 setItemgetItem

localStorage.setItem('selected_tab', 'FAQ');
localSTorage.getItem('selected_tab'); // 'FAQ'

键和值都必须是字符串。如果你想保存一个对象或数组,可以在保存时调用 JSON.stringify() 并在读取时调用 JSON.parse() 来实现。

const product = {
  id: '123',
  name: 'Coffee Beans',
};

localStorage.setItem('cached_product', JSON.stringify(product));
JSON.parse(localStorage.getItem('cached_product'));

local storage 的另一个用例是在多个选项卡之间同步数据。通过为 'storage' 事件添加侦听器,你可以在另一个选项卡或窗口中更新数据。

window.addEventListener('storage', () => {
  console.log('local storage has been updated');
});

仅当在另一个文档中修改本地或会话存储时才会触发此事件。也就是说,你无法在当前浏览器选项卡中侦听 storage 的更改。不幸的是,截至撰写本文时,存储事件监听器尚未在 Chrome 上得到支持

那么localStoragesessionStorage 之间有什么区别呢?与 cookie 不同,Web Storage API 没有过期或最大期限功能。如果使用 localStorage,除非手动删除,否则数据将无限期保留。你可以通过运行 localStorage.removeItem('key') 来删除单个键的值,或者通过运行 localStorage.clear() 清除所有数据。

如果使用 sessionStorage,则数据将仅持续到当前会话结束。如果你没有设置最大时间或过期,它将被视为与 cookie 保持的方式相似。在任何一种情况下,如果用户使用隐身,本地存储都不会在会话之间保留数据。

IndexedDB

如果 cookie 和 localStorage 都不符合你的要求,还有另一种选择:IndexedDB,一个浏览器内置的数据库系统。

localStorage 同步执行所有方法时,IndexedDB 会异步调用它们。这将会允许访问数据而不会阻塞其余代码。当你处理大量可能访问代价高昂的代码时,这非常有用。

IndexedDB 在其存储的数据类型方面也具有更大的灵活性。虽然 cookies 和 localStorage 仅限于存储字符串,但 IndexedDB 可以存储可以通过“结构化克隆算法”复制的任何类型的数据。这包括 ObjectDateFileBlobRegEx 以及更多类型

性能和灵活性增加的缺点是 IndexedDB 的 API 更低级且更复杂。幸运的是有许多库可以解决这个问题。

localForage 为 IndexedDB 提供了一个更简单的类似 localStorage 的 API。 PouchDB 提供了一个可以离线的存储 API,可以与在线 CouchDB 数据库同步。 idb 是一个小型库,具有更简单的基于 promise 的 API。 Dexie 添加了更强大的查询 API,同时保持了良好的性能。根据你的使用情况还有许多选择。

Cache API

另一种用于持久数据的专用工具是 Cache API。虽然它最初是为 service workers 创建的,但它可用于缓存任何网络请求。 Cache API 公开了 Window.caches,它提供了保存和检索响应的方法,允许你保存可永远以后访问的 RequestsResponses 对。

例如,如果你想在从 API 请求响应之前检查浏览器的缓存以获取响应,则可以执行以下操作:

const apiRequest = new Request('https://www.example.com/items');
caches.open('exampleCache') // opens the cache
  .then(cache => {
    cache.match(apiRequest) // checks if the request is cached
      .then(cachedResponse =>
        cachedResponse || // return cachedReponse if available
        fetch(apiRequest) // otherwise, make new request
          .then(response => {
            cache.put(apiRequest, response); // cache the response
            return response;
          })
        })
    .then(res => console.log(res))
})

第一次运行代码时,它将缓存响应。随后每次都会缓存请求,并且不会发出网络请求。

总结

在浏览器上存储数据的每种方法都有其自己的用途。如果信息很小,很敏感,并且可能在服务器上使用,那么 cookie 就是最佳选择。如果要保存更大且更不敏感的数据,Web Storage API 可能是更好的选择。

如果你打算存储大量结构化数据,IndexedDB 非常棒。 Cache API 用于存储来自 HTTP 请求的响应。根据你的需要,有很多工具可供使用。

其他资源和扩展阅读

你可以通过阅读 MDN 文档来获取更多信息:


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

超越 Cookie:当今的客户端数据存储技术-LMLPHP

欢迎继续阅读本专栏其它高赞文章:


08-06 17:34