神奇的网页记事本(一)— 浏览器存储技术之Cookie

前言

浏览器本身常见的存储技术有:Cookie、localStorage、sessionStorage、IndexedDB、Web SQL Database、Cache Storage 等。

  1. Cookie:用于维持请求状态的小型文本文件,由网站通过用户的浏览器创建并存储。
  2. localStorage:localStorage 是 Web Storage API 的一部分,它是一种在浏览器中存储持久化数据的机制。它允许网页在用户的浏览器中保存键值对数据,并且这些数据在浏览器关闭后仍然有效,没有过期时间。
  3. sessionStorage:sessionStorage 也是 Web Storage API 的一部分,它是一种在浏览器中存储临时会话数据的机制。与 localStorage 不同,sessionStorage 中存储的数据仅在浏览器会话期间有效,即在用户关闭浏览器选项卡或窗口后,这些数据将被清除。
  4. IndexedDB: IndexedDB 是浏览器上的索引数据库,它允许网页在用户的浏览器上进行复杂的查询和事务操作。
  5. Web SQL Database: Web SQL Database 是一个基于 SQL 的浏览器数据库,可以在客户端存储结构化数据。虽然它曾经是 HTML5 的一部分,但目前已经被弃用,不再被推荐使用。现代浏览器更倾向于支持 IndexedDB
  6. Cache Storage: Cache Storage 是 Service Worker 的一部分,允许开发者对网络请求和响应进行缓存。它可以用于在离线状态下提供快速的资源访问,以及优化网页的性能和加载速度。

除了上面提到的存储技术,还有一些不常用的存储技术,我们打开控制台的 Application 就可以看到一系列和存储相关的选项:

image.png

本篇主要是讨论前面提到的第一种浏览器存储技术: Cookie

Cookie

Cookie 的作用不是为了存储数据,而是为了 维持状态。我们都知道 http协议是无状态的,这就会导致每一次请求都是相互独立,不方便服务器识别用户身份和追踪用户行为,因此就诞生了 Cookie 技术,服务器可以设置和读取 Cookie 信息,以实现 会话跟踪身份认证 等功能。一般来说 Cookie 是由服务端生成,由客户端进行存储和维护的,浏览器在发起 http 请求时,携带 Cookie 信息发送给服务器,由服务器辨别用户身份。

为了保护用户隐私,大多数主流浏览器都禁止 JavaScript 修改 Cookie 请求头。只有当请求与当前页面的源(协议、域名和端口)相同时,浏览器才会默认自动添加 Cookie 请求头并携带相应的 Cookie 信息。如果通过 js 设置 Cookie 请求头就会发生错误:Refused to set unsafe header “cookie”

Cookie 的生成方式

  1. 服务端生成。服务端根据 http 响应头的 set-cookie 设置 Cookie。
Set-Cookie: uid=fb3eda1aa35a9ed9f88f346a7; Path=/
  1. 客户端生成。客户端的 js 通过 document.cookie 也可以读写 Cookie。
document.cookie="name=张三";
document.cookie="age=18; domain=juejin.im; path=/";

Cookie 的限制

  1. 大小限制: 单个 Cookie 的大小通常限制在 4KB 左右,所以如果一个网站设置了的 Cookie 太大,可能会导致浏览器拒绝保存新的 Cookie。
  2. 数量限制: 每个域名下的 Cookie 的个数是有上限,不同浏览器的上限不一样。如果一个网站设置了太多的Cookie,可能会导致浏览器覆盖旧的 Cookie。对于 Chrome(92) 浏览器每个 Cookie 字符串最长是 1024 个字符串(document.cookie 后面的字符串长度,包括 path、domain等,如果只有 name=value, 则 name=value 这个字符串长度最长为1024),超长的字符串会导致设置 cookie 失败。对于 Chrome 的同域名下的 cookie 个数最多为 176 个左右,不同版本可能都不太一样。
  3. 跨域限制:浏览器通常会限制来自其他域名的 Cookie 访问。当一个网页发起跨域请求时(例如使用XMLHttpRequest、Fetch API等),浏览器默认情况下不会在请求中发送跨域域名下的 Cookie 信息,即使在请求时手动增加请求头 Cookie 也会被浏览器过滤掉。再即使服务器在响应请求时设置了 Cookie,浏览器也不会将该 Cookie 存储起来,也不会在后续的跨域请求中携带这个 Cookie。

Cookie 的跨站(跨域)访问

跨站和跨域两者不是同一个概念。不同站的cookie都是第三方cookie,不跨站的cookie成为第一方cookie。关于跨站的说明可以参考下面??引用:

以下内容来源于:深入理解 Cookie 的 SameSite 属性–小小木锤
所谓的 跨站跨域 其实不是同一个概念,跨站 不是根据同源策略(协议,主机,端口)来判断,而是 PSL(公共后缀列表)。比如 foo.example.combar.example.com 就不属于 跨站,因为他们同属于 example.com 的子域名。这里也不能简单理解为二级域名相同,比如 foo.github.iobar.github.io,虽然都是 github.io 的子域名,但是他们之间是跨站的,因为 github.io 是在 PSL(公共后缀列表) 中的,相当于顶级域名,可以在 此处 查看哪些域名是属于 PSL 的。还有就是协议和域名相同时,如果端口不同,两个页面也会认为是同站的;但是如果域名和端口相同,协议不同,这时候就认为这两个页面是跨站

要实现 Cookie 的跨站访问需要前后端协调开发,下面是跨站请求读写 Cookie 的前提(必需全部满足):

  1. 前端配置 withCredentials(允许请求时携带凭据)。
    • 原生 XMLHttpRequest: (new XMLHttpRequest()).withCredentials = true;
    • axios: axios.defaults.withCredentials = true;
    • fetch请求fetch(url, { credentials: true })
  2. 前端页面的域名在后端允许跨域的白名单中(Access-Control-Allow-Origin 中包括前端页面所在域名)。
  3. 后端也需要允许请求时携带凭据(响应头:Access-Control-Allow-Credentials:true)。
  4. 在新版(v80+)的 Chrome 浏览器中,只有指定 Cookie 的 SameSite 属性为 None 且 Secure 属性为 true 才可以设置第三方 Cookie(因为chrome80+中,samesite默认值是lax);如果域名相同,但是端口或者协议不同 samesite 的值也可以是lax。

image.png

Cookie 各个键的说明

从下图可以看出 Cookie 是以键值对的形式存在的,每个 Cookie 的信息包括 Name、Value、Domain、Path、Expires/Max-age、Size、HttpOnly、Secure、SameSite、Party Key、Priority 字段,各个键名都不区分大小写。
image.png

Name & Value

Name

Cookie 的 Name(键名) 是大小写敏感的,即 age 和 Age 是两个不一样的键名。在同一个页面下,可以存在多个同名的 Cookie,前提是它们的 Domain 或者 Path不同;如果 Cookie 的 Name、Domain、Path 三者都一致的话,后面设置的 Cookie 会覆盖前面设置的。

image.png

关于命名上的要求:

  • 必须是字符串(可以为空,不建议为空,因为不同浏览器、服务器支持性不一致)
  • 不包括控制字符(CTL)的 US-ASCII 字符串
  • 不能含有 ;=(应该还有其他的特殊字符没有列出)
    image.png

建议键名是只包含英文字母(a-zA-Z)、数字(0-9)、下划线(_)和中横线(-)的语义明确的字符串。如果键名设置不规范的话,可能会导致 Cookie 设置不成功或者乱码现象(我在chrome中可以将name设置为中文,但是作为请求头发送给服务器就会乱码)

Value

Cookie 的值,规范的取值可以参考RFC6265文档

image.png

“cookie-octet” 是指允许在Cookie值中使用的ASCII字符,它包括:
%x21:ASCII值为33的字符,即叹号 !
%x23-2B:ASCII值从35到43的字符,即 # $ % & ' ( ) * +
%x2D-3A:ASCII值从45到58的字符,即 - . / 0-9 :
%x3C-5B:ASCII值从60到91的字符,即 < = > ? @ A-Z [
%x5D-7E:ASCII值从93到126的字符,即 ] ^ _ ` a-z { | } ~
除了上述字符外,其他所有控制字符(CTLs)、空白字符(空格、换行等)、双引号(DQUOTE)、逗号(comma)、分号(semicolon)和反斜杠(backslash)都不允许在Cookie值中出现。我在chrome中测试貌似只有分号不行,其他的好像可以,但是中文在请求时会乱码

如果Cookie的键值中包含空格、特殊符号或其他可能引起问题的字符,建议使用URL编码(encodeURIComponent)对其进行编码,确保Cookie在传输过程中不会出现问题。

Domain & Path

Domain 和 Path 一起标识了 Cookie 的作用域,即允许 Cookie 应该发送给哪些 URL。Domain 指定了哪些域名可以接受 Cookie,如果不指定,默认为当前页面所在域名(即:location.host),如果指定了 Domain,则一般包含子域名,所以该 cookie 的作用域将会更广。

Domain

假如我当前所在页面的地址为: test.cookie.com/ ,则默认的 Domain 为 test.cookie.com ,也可以将 Domain 的值设置为 .cookie.com,但不能设置为 new.test.cookie.com,更不能设置为顶级域名(.com)。需要注意的是 .cookie.com 前面的 .,这个至关重要,因为不加上这个点表示仅当前页面有效,不会在其子域名下共享,同时顶级域名不是一个具体有效的域名,因此 Domain 的值也不能设置为顶级域名。

通过 document.cookie="age=20;domain=cookie.com"Set-Cookie: age=20;domain=cookie.com 设置是有效的,这时候浏览器会自动加上前缀 .,如果直接在控制台的 Application 中修改 Domain 的值为 cookie.com 是会导致 cookie 失效的(该cookie将会在当前页面消失,在 cookie.com/ 页面中可以找到)

下表是各个 Domain 值对应的域名生效对比:

Domain 参数 test.cookie.com new.test.cookie.com new.cookie.com cookie.com
test.cookie.com
cookie.com
.cookie.com
.test.cookie.com
Path

Cookie 的 Path 默认值是当前文档所在位置(即 location.pathname 最后一个斜杠前面的内容)。如果当前页面的 URL 是 https://cookie.com/example/test,那么默认情况下,该 Cookie 的 path 将是 /example,只有在路径以 /example 开头的页面才能访问这个 Cookie。下表是一些默认的 Path 的例子:

页面 URL Cookie Path
cookie.com/ /
cookie.com/example /
cookie.com/example/ /example
cookie.com/example/tes… /example
cookie.com/example?nam… /

如果 Path 不为 /,只有当页面 location.pathname 和 Path 值相匹配(location.pathname 的前缀是 Path 值,且 Path 值是 location.pathname 以某一个 / 分割的完整的路径)时才可以查看(控制台的 Application 中查看)和读写(document.cookie)对应的 Cookie。即当 Path 是 /example 时,页面 https://cookie.com/example 和页面 https://cookie.com/example/test 可以查看和读写,但是页面 https://cookie.com/ 和页面 https://cookie.com/examplefull 无法读写和查看的。

在请求时,也需要请求 URL 和 Path 匹配(URL 的前缀是 Path 值,且 Path 值是 URL 以某一个 / 分割的完整的路径),只有匹配时浏览器才会默认在 Cookie 请求头中携带对应的 cookie。

假设现在以下 Cookie:

  1. name1=zhangsan;path=/;
  2. name2=lisi;path=/test;
  3. name3=wangwu;path=/tes;

则在页面中有以下表现:

页面URL Cookie name1 Cookie name2 Cookie name3
cookie.com/ 可读写、可查看 不可读写、不可查看 不可读写、不可查看
cookie.com/test 可读写、可查看 可读写、可查看 不可读写、不可查看
cookie.com/test/new 可读写、可查看 可读写、可查看 不可读写、不可查看

请求时有以下表现(必需同源):

请求URL Cookie name1 Cookie name2 Cookie name3
/public 默认携带 不携带 不携带
/test 默认携带 默认携带 不携带

Expires/Max-age

Expires/Max-age 可以分为两个不同的属性 ExpiresMax-age。这两个属性都是设置 Cookie 有效期的,Expires 的优先级比 Max-age 低,但是兼容性更好。如果 ExpiresMax-age 都没有设置,则 Cookie 将在会话(Session)结束时过期。

image.png

Expires

Expires 指定的是 Cookie 的有效期,设置时标准值为 GMT 格式的时间戳。不规范的参数将会导致 Expires 属性被忽略。

document.cookie="name=zhangsan;Expires=Tue, 29 Aug 2024 14:45:25 GMT";
// document.cookie="name=zhangsan;Expires=2023-08-02T16:13:29.910Z"; // 无效
// document.cookie="name=zhangsan;Expires=666"; // 无效
Max-age

Max-age 设置 cookie 的有效期,单位是秒,该属性不区分大小写。Max-age 的权重比 Expires 大,两个同时设置时,后者会被忽略。Max-age 的值必须是整数(设置成功后浏览器控制台中显示的值是一个具体的时间点),可以正负整数或0,如果设置不规范 Max-age 属性将会被忽略。当 Max-age 的值为:

  • 正整数:Cookie 的有效期是 当前时间 + Max-age
  • 负数或0:Cookie 立即过期(即删除该Cookie)。
  • 不合法:Max-age 会被忽略,如果有 Expires,则 Cookie 的有效期由 Expires 指定,否则 Cookie 的有效期为 Session。
document.cookie="name1=zhangsan;Max-age=60;Expires=Tue, 29 Aug 2024 14:45:25 GMT"; // 60秒后过期
document.cookie="name2=zhangsan;Max-age=0;Expires=Tue, 29 Aug 2024 14:45:25 GMT"; // 立即过期
document.cookie="name3=zhangsan;Max-age=a;Expires=Tue, 29 Aug 2024 14:45:25 GMT"; // Cookie 的过期时间为 Tue, 29 Aug 2024 14:45:25 GMT

Size

Cookie 的大小,由浏览器计算。

HttpOnly

HttpOnly 是包含在 Set-Cookie 响应头文件中的附加标志,表示禁止浏览器通过脚本读写该 Cookie,Documen.cookie API 无法对含有 httponly 标记的 Cookie 进行增删改查,主要是防止跨域脚本攻击(XSS)。
image.png

Secure

在 http 请求时,只有在浏览器认为安全的情况下才允许在请求时候携带 Cookie。下面是浏览器认为安全的三种情况:

  • 请求协议是 https
  • 请求的域名为 localhost
  • 请求的域名为 127.0.0.1

SameSite

Cookie 的 SameSite 属性用来限制第三方 Cookie,从而减少安全风险,该属性有三个值:

  • Strict: 最严格的,完全禁用第三方 Cookie,禁止任何跨站请求发送 Cookie。
  • Lax: Lax 规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。协议或者端口不同但域名相同时(即同站时)也可以发送cookie。
    mp.weixin.qq.com/s?__biz=Mzk…
  • None: 其他浏览器的默认值(chrome低版本的默认值也是 None),表示允许跨域请求访问该 Cookie (前提是请求时配置了允许携带)

在Chrome80+浏览器中,如果要设置 SameSite=None,前提是要设置 Secure=true, 否则 Cookie 就会不生效。

// 在 Chrome v96 中
// Set-Cookie: name=zhangsan; SameSite=None // 无效,浏览器会警告
Set-Cookie: name=zhangsan; SameSite=None; Secure // 有效

image.png

Party Key

目前该属性还在提案阶段,可参考:github.com/cfredric/sa…juejin.cn/post/700201…

Priority

这是一个很少用到的属性,因为到目前为止,该属性只有 Chrome 实现了。

内容节选自:asnokaze.hatenablog.com/entry/2019/…
Chrome于2013年实现了它(相应的commit),并于2016年提交了提议的规范“ A Retention Priority Attribute for HTTP Cookies ”,但尚未标准化。

该属性有三个值,分别是:HighMediumLow,用于指示 Cookie 的优先级,优先级越低越先被移除(每个域名下的 Cookie 数量是有限的,超过上限浏览器默认会优先移除最早添加的,加上优先级就默认先移除优先级最低的),默认值为:Medium

封装的操作 Cookie 的方法

interface CookieOptions {
  maxAge: number;
  expires: string;
  path: string;
  domain: string;
  secure: boolean;
  sameSite: 'none' | 'lax' | 'strict';
}

// 设置 Cookie
function setCookie(name: string, value: string, options: CookieOptions) {
  let newCookie = `${name}=${encodeURIComponent(value)}`;
  if (options?.expires) {
    newCookie += `; expires=${options.expires}`;
  }
  if (options?.maxAge) {
    const expires = new Date(Date.now() + options.maxAge * 1000).toUTCString();
    newCookie += `; max-age=${options.maxAge}; expires=${expires}`;
  }
  if (options?.domain) {
    newCookie += `; domain=${options.domain}`;
  }
  if (options?.path) {
    newCookie += `; path=${options.path}`;
  }
  if (options?.secure) {
    newCookie += `; secure`;
  }
  if (options?.sameSite) {
    newCookie += `; SameSite=${options.sameSite}`;
  }
  document.cookie = newCookie;
}

// 获取 Cookie(由于document.cookie获取到的cookie是不区分domain和path的,所以无法获取指定domain或path的cookie值)
function getCookie(name: string): string | null {
  const cookies = document.cookie.split(';');
  console.log(cookies);
  for (let i = 0; i < cookies.length; i++) {
    const cookie = cookies[i].trim();
    if (cookie.startsWith(name + '=')) {
      return cookie.substring(name.length + 1);
    }
  }
  return null;
}

// 删除 Cookie
function deleteCookie(
  name: string,
  options?: { domain?: string; path?: string }
) {
  let newCookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; max-age=0`;
  if (options?.domain) {
    newCookie += `; domain=${options.domain}`;
  }
  if (options?.path) {
    newCookie += `; path=${options.path}`;
  }
  document.cookie = newCookie;
}

Cookie、localStorage、sessionStorage、IndexedDB的对比

下面是 Cookie、localStorage、sessionStorage 和 IndexedDB 的对比:

特点 存储容量 生命周期 跨会话 目的
Cookie 通常几KB 可设置过期时间 在客户端和服务器间传递少量信息,如用户认证信息、会话标识等
localStorage 通常几兆字节 永久存储 长期保存用户数据,如偏好设置、登录状态等
sessionStorage 通常几兆字节 当前会话期间 临时保存会话期间数据,如表单传递
IndexedDB 通常较大 永久存储 存储大量结构化数据,如离线应用、缓存数据等

总结:

  • 如果你需要在不同会话间共享数据,使用 localStorageCookie
  • 如果你需要在当前会话期间共享数据,使用 sessionStorage
  • 如果你需要在客户端存储较大的结构化数据,使用 IndexedDB

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYG5zpkH' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片