🪂现代 JavaScript —— 杂项(在浏览器中存储数据)
00 分钟
2022-9-13
2022-9-15
type
status
date
slug
summary
tags
category
icon
password
Edited
Sep 15, 2022 01:17 PM
Created
Sep 13, 2022 02:14 PM

Cookie

Cookie 是直接存储在浏览器中的一小串数据。它们是 HTTP 协议的一部分,由 RFC 6265 规范定义。
Cookie 通常是由 Web 服务器使用响应 Set-Cookie HTTP-header 设置的。然后浏览器使用 Cookie HTTP-header 将它们自动添加到(几乎)每个对相同域的请求中。
使用 document.cookie 属性从浏览器访问 cookie

document.cookie

document.cookie 的值由 name=value 对组成,以 ; 分隔。每一个都是独立的 cookie。
document.cookie 是一个 访问器(getter/setter)。对其的赋值操作会被特殊处理。
对 document.cookie 的写入操作只会更新其中提到的 cookie,而不会涉及其他 cookie。
因为 document.cookie= 操作不是重写整所有 cookie。它只设置代码中提到的 cookie user
 
从技术上讲,cookie 的名称和值可以是任何字符。为了保持有效的格式,它们应该使用内建的 encodeURIComponent 函数对其进行转义
⚠️
限制
  • encodeURIComponent 编码后的 name=value 对,大小不能超过 4KB。因此,我们不能在一个 cookie 中保存大的东西。
  • 每个域的 cookie 总数不得超过 20+ 左右,具体限制取决于浏览器。

参数

path

path=/mypath
url 路径前缀必须是绝对路径。它使得该路径下的页面可以访问该 cookie。默认为当前路径。
如果一个 cookie 带有 path=/admin 设置,那么该 cookie 在 /admin 和 /admin/something 下都是可见的,但是在 /home 或 /adminpage 下不可见。
通常,我们应该将 path 设置为根目录:path=/,以使 cookie 对此网站的所有页面可见。

domain

domain=site.com
domain 控制了可访问 cookie 的域。但是在实际中,有一些限制。我们无法设置任何域。
默认情况下,cookie 只有在设置的域下才能被访问到。
请注意,默认情况下,cookie 也不会共享给子域,例如 forum.site.com
如果我们想允许像 forum.site.com 这样的子域在 site.com 上设置 cookie,也是可以实现的。
因此,当在 site.com 设置 cookie 时,我们应该明确地将 domain 选项设置为根域:domain=site.com。那么,所有子域都可以访问到这样的 cookie。

expires & max-age

默认情况下,如果一个 cookie 没有设置这两个参数中的任何一个,那么在关闭浏览器之后,它就会消失。此类 cookie 被称为 "session cookie”。
为了让 cookie 在浏览器关闭后仍然存在,我们可以设置 expires 或 max-age 选项中的一个。
expires=Tue, 19 Jan 2038 03:14:07 GMT
日期必须完全采用 GMT 时区的这种格式(可以使用 date.toUTCString 来获取它
示例:一天后过期
max-age=3600
指明了 cookie 的过期时间距离当前时间的秒数(上例就是1h后过期)

secure

secure
Cookie 应只能被通过 HTTPS 传输。
默认情况下,如果我们在 http://site.com 上设置了 cookie,那么该 cookie 也会出现在 https://site.com 上,反之亦然。
使用此选项,如果一个 cookie 是通过 https://site.com 设置的,那么它不会在相同域的 HTTP 环境下出现,例如 http://site.com
如果一个 cookie 包含绝不应该通过未加密的 HTTP 协议发送的敏感内容,那么就应该设置 secure 标识。

samesite

这是另外一个关于安全的特性。它旨在防止 XSRF(跨网站请求伪造)攻击。
Cookie 的 samesite 选项提供了另一种防止此类攻击的方式,(理论上)不需要要求 “XSRF 保护 token”。
  • samesite=strict(和没有值的 samesite 一样)
    • 如果用户来自同一网站之外,那么设置了 samesite=strict 的 cookie 永远不会被发送。
      换句话说,无论用户是通过邮件链接还是从 evil.com 提交表单,或者进行了任何来自其他域下的操作,cookie 都不会被发送。
  • samesite=lax
    • 一种更轻松的方法,该方法还可以防止 XSRF 攻击,并且不会破坏用户体验。
      宽松(lax)模式,和 strict 模式类似,当从外部来到网站,则禁止浏览器发送 cookie,但是增加了一个例外。
      如果以下两个条件均成立,则会发送含 samesite=lax 的 cookie:
      1. HTTP 方法是“安全的”(例如 GET 方法,而不是 POST)。
        1. 所有安全的 HTTP 方法详见 RFC7231 规范。基本上,这些都是用于读取而不是写入数据的方法。它们不得执行任何更改数据的操作。跟随链接始终是 GET,是安全的方法。
      1. 该操作执行顶级导航(更改浏览器地址栏中的 URL)。
        1. 这通常是成立的,但是如果导航是在一个 <iframe> 中执行的,那么它就不是顶级的。此外,用于网络请求的 JavaScript 方法不会执行任何导航,因此它们不适合。

httpOnly

这个选项禁止任何 JavaScript 访问 cookie。我们使用 document.cookie 看不到此类 cookie,也无法对此类 cookie 进行操作。
这是一种预防措施,当黑客将自己的 JavaScript 代码注入网页,并等待用户访问该页面时发起攻击,而这个选项可以防止此时的这种攻击。这应该是不可能发生的,黑客应该无法将他们的代码注入我们的网站,但是网站有可能存在 bug,使得黑客能够实现这样的操作。

XSRF攻击

想象一下,你登录了 bank.com 网站。此时:你有了来自该网站的身份验证 cookie。你的浏览器会在每次请求时将其发送到 bank.com,以便识别你,并执行所有敏感的财务上的操作。
现在,在另外一个窗口中浏览网页时,你不小心访问了另一个网站 evil.com。该网站具有向 bank.com 网站提交一个具有启动与黑客账户交易的字段的表单 <form action="https://bank.com/pay"> 的 JavaScript 代码。
你每次访问 bank.com 时,浏览器都会发送 cookie,即使该表单是从 evil.com 提交过来的。因此,银行会识别你的身份,并执行真实的付款。
notion image
这就是所谓的“跨网站请求伪造(Cross-Site Request Forgery,简称 XSRF)”攻击。

Cookie 函数

getCookie(name)

setCookie(name, value, options)

deleteCookie(name)

GDPR

💡
GDPR
欧洲有一项名为 GDPR 的立法,该法规针对网站尊重用户实施了一系列规则。其中之一就是需要明确的许可才可以跟踪用户的 cookie。
这仅与跟踪/识别/授权 cookie 有关。
所以,如果我们设置一个只保存了一些信息的 cookie,但是既不跟踪也不识别用户,那么我们可以自由地设置它。
但是,如果我们要设置带有身份验证会话(session)或跟踪 id 的 cookie,那么必须得到用户的允许。
 

LocalStorage & sessionStorage

Web 存储对象 localStorage 和 sessionStorage 允许我们在浏览器上保存键/值对。
它们有趣的是,在页面刷新后(对于 sessionStorage)甚至浏览器完全重启(对于 localStorage)后,数据仍然保留在浏览器中。
与 cookie 不同,Web 存储对象不会随每个请求被发送到服务器。因此,我们可以保存更多数据。大多数现代浏览器都允许保存至少 5MB 的数据(或更多),并且具有用于配置数据的设置。

方法和属性

两个存储对象都提供相同的方法和属性:
  • setItem(key, value) —— 存储键/值对。
  • getItem(key) —— 按照键获取值。
  • removeItem(key) —— 删除键及其对应的值。
  • clear() —— 删除所有数据。
  • key(index) —— 获取该索引下的键名。
  • length —— 存储的内容的长度。

localStorage

localStorage 最主要的特点是:
  • 在同源的所有标签页和窗口之间共享数据。
  • 数据不会过期。它在浏览器重启甚至系统重启后仍然存在。
只需要在同一个源(域/端口/协议),URL 路径可以不同。在所有同源的窗口之间localStorage 数据可以共享。因此,如果我们在一个窗口中设置了数据,则在另一个窗口中也可以看到数据变化。
请注意,键和值都必须是字符串。

遍历

for in 它会遍历所有的键,但也会输出一些我们不需要的内建字段。
使用 Object.keys 获取只属于“自己”的键

sessionStorage

sessionStorage 对象的使用频率比 localStorage 对象低得多。
属性和方法是相同的,但是它有更多的限制:
  • sessionStorage 的数据只存在于当前浏览器标签页。
    • 具有相同页面的另一个标签页中将会有不同的存储。
    • 但是,它在同一标签页下的 iframe 之间是共享的(假如它们来自相同的源)。
  • 数据在页面刷新后仍然保留,但在关闭/重新打开浏览器标签页后不会被保留。
sessionStorage 不仅绑定到源,还绑定在同一浏览器标签页。

Storage 事件

当 localStorage 或 sessionStorage 中的数据更新后,storage 事件就会触发,它具有以下属性:
  • key —— 发生更改的数据的 key(如果调用的是 .clear() 方法,则为 null)。
  • oldValue —— 旧值(如果是新增数据,则为 null)。
  • newValue —— 新值(如果是删除数据,则为 null)。
  • url —— 发生数据更新的文档的 url。
  • storageArea —— 发生数据更新的 localStorage 或 sessionStorage 对象。
重要的是:该事件会在所有可访问到存储对象的 window 对象上触发,导致当前数据改变的 window 对象除外。
如果两个窗口都在监听 window.onstorage 事件,那么每个窗口都会对另一个窗口中发生的更新作出反应。
并且,event.storageArea 包含存储对象 —— sessionStorage 和 localStorage 具有相同的事件,所以 event.storageArea 引用了被修改的对象。我们可能会想设置一些东西,以“响应”更改。
这允许同源的不同窗口交换消息。
 

IndexedDB

IndexedDB 是一个浏览器内建的数据库,它比 localStorage 强大得多。
  • 通过支持多种类型的键,来存储几乎可以是任何类型的值。
  • 支撑事务的可靠性。
  • 支持键值范围查询、索引。
  • 和 localStorage 相比,它可以存储更大的数据量。
IndexedDB 适用于离线应用,可与 ServiceWorkers 和其他技术相结合使用。
基本用法可以用几个短语来描述:
  1. 获取一个 promise 包装器,比如 idb。
  1. 打开一个数据库:idb.openDb(name, version, onupgradeneeded)
      • 在 onupgradeneeded 处理程序中创建对象存储和索引,或者根据需要执行版本更新。
  1. 对于请求:
      • 创建事务 db.transaction('books')(如果需要的话,设置 readwrite)。
      • 获取对象存储 transaction.objectStore('books')
  1. 按键搜索,可以直接调用对象库上的方法。
      • 要按对象字段搜索,需要创建索引。
  1. 如果内存中容纳不下数据,请使用光标。
IndexedDB
IndexedDB 是一个浏览器内建的数据库,它比 localStorage 强大得多。 通过支持多种类型的键,来存储几乎可以是任何类型的值。 支撑事务的可靠性。 支持键值范围查询、索引。 和 localStorage 相比,它可以存储更大的数据量。 对于传统的 客户端-服务器 应用,这些功能通常是没有必要的。IndexedDB 适用于离线应用,可与 ServiceWorkers 和其他技术相结合使用。 根据规范 https://www.w3.org/TR/IndexedDB 中的描述,IndexedDB 的本机接口是基于事件的。 我们还可以在基于 promise 的包装器(wrapper),如 https://github.com/jakearchibald/idb 的帮助下使用 async/await 。这要方便的多,但是包装器并不完美,它并不能替代所有情况下的事件。因此,我们先练习事件(events),在理解了 IndexedDB 之后,我们将使用包装器。 不同的浏览器和操作系统级别的用户都有各自独立的存储。 要想使用 IndexedDB,首先需要 open (连接)一个数据库。 语法: 数据库可以有许多不同的名称,但是必须存在于当前的源(域/协议/端口)中。不同的网站不能相互访问对方的数据库。 调用之后会返回 openRequest 对象,我们需要监听该对象上的事件: success:数据库准备就绪,openRequest.result 中有了一个数据库对象"Database Object",我们应该将其用于进一步的调用。 error:打开失败。 upgradeneeded:数据库已准备就绪,但其版本已过时(见下文)。 IndexedDB 具有内建的"模式(scheme)版本控制"机制,这在服务器端数据库中是不存在的。 与服务器端数据库不同,IndexedDB 存在于客户端,数据存储在浏览器中。因此,开发人员无法随时都能访问它。因此,当我们发布了新版本的应用程序,用户访问我们的网页,我们可能需要更新该数据库。 如果本地数据库版本低于 open
IndexedDB
 

参考链接:
  1. 在浏览器中存储数据 (javascript.info)
上一篇
现代 JavaScript —— 杂项(动画)
下一篇
可恢复的文件上传 & 文件下载

评论
Loading...