细读 JS | Cookie 详解

本文涉及的产品
.cn 域名,1个 12个月
简介: 细读 JS | Cookie 详解

正文


一、Cookie 简述


1. Cookie 是什么?


“Cookie” 这个词没有太多的含义,在计算机学科中很早就出现了,用来表示少量文本数据。

在前端领域,我认为 Cookie 的概念是很模糊的。有的时候,可以将它理解为一个小型文本,也可以理解为一种客户端(下称“浏览器”)与服务端(下称“服务器”)交互的一种存储机制。比如,同事 A 说让我看看那个 XXX Cookie 是多少,它具体指浏览器中存储的一小块文本。如果将 Cookie 与服务器 Session 放一起描述的时候,它指的是一种交互机制。

Cookie 是不安全的,原因是它本身没有采用任何加密机制。通过 HTTPS 来传输 Cookie 数据是安全的,它与 Cookie 本身无关,与 HTTPS 协议相关。

在 Web 中,Cookie 可被服务器、浏览器进行读写操作。在浏览器与服务器进行交互的过程中,浏览器会将任意 Cookie 写入 HTTP 头部,传输给服务端。随着 Web 的飞速发展,出于安全性的考虑,标准与浏览器都在往前走,因而并不是所有 Cookie 都会出现在 HTTP 头部。具体行为下文再谈。


Cookie 是一个小型文本文件,用于浏览器与服务器的数据传输。


2. 为什么需要 Cookie?


我们都知道 HTTP 是无状态的。通俗地讲, 同一浏览器连续发送 HTTP 请求两次,服务器也无法识别到两个请求是来自同一客户端的。

但并不意味着,HTTP 协议的无状态是不好的。只是一些场景下,我们需要来维护“状态”(指的是客户端与服务端会话的状态,而不是说给 HTTP 协议加个状态,这是不对的,也无法做到)。

比如,网上购物、网络聊天、发布评论等场景,是需要维护状态的。试想一下,如果没有状态,你添加至购物车的商品,刷新下页面就不见了,那还不得急......解决方案也很多,比如给请求打上一个“标记”即可,在添加至购物车的请求上带上 userIdgoodsId 并发送服务端,服务端拿到这些信息就可以区分来自哪个用户的了,并存储到相应的表。这里提到的 userIdgoodsId 都是标记。

那一系列的问题来了,标记来自哪里、如何存储、发送请求如何带上、服务端如何接受?

  • 标记一般来自服务端
  • 客户端存储标记的方式有很多,比如常见的有:全局变量、CookiesessionStoragelocalStorage,再有更新的 IndexedDBCache API 等接口。

Cookie 仅仅只是其中一种存储方式而已,但它可以做到在服务端写、客户端读,然后发起请求时,自动携带在 HTTP 头部,服务端可以接收到。是一种可以做到无感的读写方案。同时,正是因为这种请求时自动带上的机制,被一些别有用心的人利用它来做一些坏事,比如 XSS、CSRF 等。

在这些存储方案里,Cookie 是最早出现的,所有浏览器都支持。随着 Web Storage 的发展,又出现了诸如 sessionStoragelocalStorage 等方案。如果还不够用,还有可存储大量结构化数据的解决方案 IndexedDB

随着 Web Storage 的普及,Cookie 将会回到最初的形态,作为一种被服务端脚本使用的客户端存储机制。


二、Cookie 属性


Cookie 主要是用来服务服务器的,在浏览器里,每个域 Cookie 的空间限制不超过 4K,因而不适宜用于存储与服务器无关的数据。

以下这些元数据,用来表示 Cookie 的文本数据、有效期、作用域、可访问性。

  • NameValue - 对应 Cookie 的名称和真实数据。
  • DomainPath - 决定谁可以读写这个 Cookie,类似于作用域。
  • ExpiresMax-Age - 决定了这个 Cookie 的寿命(有效期)。
  • SecureHttpOnly - 用来应对 XSS(跨站脚本攻击)。
  • SameSite - 用来应对 CSRF(跨站请求伪造)。


先看看一个真实的 Cookie 吧,如下:


60.webp.jpg

网络异常,图片无法展示
|

1. Name、Value


没什么好说的,对应 Cookie 的名称和数据,属于 Key-Value 键值对形式。Cookie 一旦创建,名称就不能更改了。Value 通常要进行编码处理。

2. Domain、Path


Domain 决定了浏览器发出 HTTP 请求时,哪些域名会携带这个 Cookie。Path 则在 Domain 的基础上,进一步约束了该 Cookie 的可访问路径。

注意点:

  • 若没有指定 Domain 属性,则默认设为当前 URL 的域名,即对应 window.location.hostdocument.domain 的值。
  • 若没有指定 Path 的话,默认为 /
  • 另外,在百度的页面下,不能将 Domain 设置成阿里的。即使不会报错,但它无效的,浏览器会自动忽略。

举个例子:

假设 Cookie 的 Domain 是 .example.com,那么发起 a.example.comb.example.com 域名下的 HTTP 请求,都会携带该 Cookie,反之不行。

假设 Cookie 的 Path 为 /user,那么请求路径为 /user/1 会带上;若请求路径为 //config 则不会带上。

通俗地讲,

Domain:你是 A 公司的员工,那么 B 公司就不能使唤你。

Path:你是 C 部门的人,那么 D 部门就不能使唤你干活。


3. Expires、Max-Age


  • 若没有指定 Expires 和 Max-Age 时,这个 Cookie 属于 Session Cookie,退出浏览器就会被清除。
  • Expires 用于设置 Cookie 的过期时间,它的值是 UTC 格式。
  • Max-Age 用于指定从现在开始 Cookie 存在的秒数,比如:60 * 60 * 24 * 7 表示一周。
  • 若同时指定 Expires 和 Max-Age,那么 Max-Age 优先生效。
  • 若到了失效时间,浏览器会自动清除相应的 Cookie。

注意点:

  • Session Cookie 退出浏览器时,有些浏览器不一定会清除,原因请看这里
  • Expires 依赖于本地系统时间,因此是不可靠的。而 Max-Age 则与本地时间无关,它总会在设置完的多少秒之后失效。
  • 若想通过脚本去删除某个 Cookie,可以将 Expires 设为过去的时间,比如 new Date(0)。也可将 Max-Age 设为 0
  • 将 Max-Age 设为负数,也相当于会话级 Cookie。
  • 在 HTTP/1.0 是通过 Expires 来判断的,而 HTTP/1.1 则通过 Max-Age 来判断,若无此参数,则降级使用 Expires 判断。


4. Secure、HttpOnly


Secure:

  • 若 Cookie 指定了 Secure,它只会在 HTTPS 请求中携带上。
  • 若当前协议是 HTTP,那么浏览器会自动忽略服务器发来的 Secure 属性。
  • Secure 只是一个开关,不需要指定值。如果通信协议是 HTTPS 协议,通过服务器写入的方式,将会自动打开。

HttpOnly:

若 Cookie 指定了 HttpOnly 属性,那么这个 Cookie 将无法通过 JavaScript 脚本获取。只有在浏览器发出 HTTP 请求时才会携带上该 Cookie。这个主要是用来解决 XSS 攻击的。


5. SameSite


在 Chrome 51 中引入这个 SameSite 属性,那时默认值为 None。从 Chrome 80 开始,默认值改为 Lax

Cookie 的 SameSite 属性用来限制第三方 Cookie,从而减少安全风险。它可以设置三个值:

  • Strict - 最为严格,完全禁止第三方 Cookie,跨站请求时,任何情况都不会发送 Cookie。只有当前页面 URL 与请求 URL 一致,才会带上 Cookie。
  • Lax - 规则稍稍放宽,大多数情况下,也是不发送第三方 Cookie,但是导航到目标网站的 GET 请求除外,只包括三种情况:链接,预加载请求,GET 表单(详见下表)。
  • None - 若浏览器的 SameSite 不为 None 时,网站可以显式关闭 SameSite 属性,将其设为 None。不过前提是必须同时设置 Secure 属性(Cookie 只能通过 HTTPS 协议发送),否则无法显式关闭,即设置无效。
请求类型 示例 None Lax
链接 <a href="..."></a> 发送 Cookie 发送 Cookie
预加载 <link rel="prerender" href="..."/> 发送 Cookie 发送 Cookie
GET 表单 <form method="GET" action="..."> 发送 Cookie 发送 Cookie
POST 表单 <form method="POST" action="..."> 发送 Cookie 不发送
iframe <iframe src="..."></iframe> 发送 Cookie 不发送
AJAX $.get("...") 发送 Cookie 不发送
Image <img src="..."> 发送 Cookie 不发送


设置了 StrictLax 以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。


设置为 Strict

Set-Cookie: CookieName=CookieValue; SameSite=Strict;


设置为 Lax

Set-Cookie: CookieName=CookieValue; SameSite=Lax;


设置为 None

// ❌ 无效
Set-Cookie: widget_session=abc123; SameSite=None
// ✅ 有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure


额外地,Cookie 和 CSRF 的关系是什么?


CSRF 攻击,仅仅是利用了 HTTP 携带 Cookie 的特性进行攻击的,但是攻击站点还是无法得到被攻击站点的 Cookie。这个和 XSS 不同,XSS 是直接通过拿到 Cookie 等信息进行攻击的。


6. Priority


优先级,这是 Chrome 的提案,定义了三种优先级,Low/Medium/High,当 cookie 数量超出时,低优先级的 cookie 会被优先清除。在 Safari 和 FireFox 中,不存在 Priority 属性。

不同浏览器有不同的清除策略:一些是替换掉最先(老)的 Cookie,有些则是随机替换。


7. SameParty


前面不是提到 SameSite 会禁用第三方 Cookie 嘛,这个 SameParty 就是为了合法地使 Cookie 在一些第三方站点下也可使用(指的是发起 HTTP 请求时会带上)。它需要配合 First-Party Sets 策略使用。

Set-Cookie: name=frankie; Secure; SameSite=Lax; SameParty

这个是新特性,更多请看这里


三、Cookie 读写


我们知道,Cookie 的读写是有差异的,读取的时候可以一次性获取当前作用域下的所用 Cookie,而写入的时候只能一条一条地进行写入操作。这跟浏览器与服务器之间 Cookie 的通信格式有关。浏览器向服务器发送 Cookie 的时候,是一行将所有 Cookie 全部发送。

关于 document.cookie 的读写差异,就是对象的 set/get 的原因。

通过以下方式,可以窥探一二,可以知道它们都是函数。但由于是原生的内置方法,因此无法打印出具体的函数内容。

// 这种方式已废弃
document.__lookupSetter__('cookie') // ƒ cookie() { [native code] }
document.__lookupGetter__('cookie') // ƒ cookie() { [native code] }
// 现在标准推荐用这个,但注意要 Document.prototype 上面找,因为它不会往原型上找的。
const descriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie')
descriptor.set // ƒ cookie() { [native code] }
descriptor.get // ƒ cookie() { [native code] }
// 若要覆盖,可以这样写(实际项目中千万别重写,除非另起名称)
Object.defineProperty(document, 'cookie', {
  set: function () {
    console.log('custom setter method...')
    // do something...
  },
  get: function () {
    console.log('custom getter method...')
    // do something and return some value
  }
})


1. 浏览器禁用 Cookie


通过以下语句,可判断浏览器是否打开了 Cookie 功能,返回一个布尔值。

window.navigator.cookieEnabled // true 开启


若关闭了 Cookie 功能,无论是服务器 Set-Cookie,还是客户端通过 JS 脚本,都无法写入 Cookie。同样地,发起 HTTP 请求也将无法携带。


2. 写入 Cookie


在服务端,各种后端语言或框架林立,写入 Cookie 的方式也各不相同。下面以 express 为例:

import express from 'express'
const app = express()
const port = 8080
app.get('/', (req, res) => {
  // 通常 name 和 value 会经过编码处理的,像 express 默认采用 encodeURIComponent 进行编码处理,亦可在以下可选项中传入 encode 属性,它接受一个函数。
  res.cookie('cookie-name', 'cookie-value', {
    // 以下为可选项
    // domain, // 默认为 URL 对应域名(注意是服务器 URL)
    // path, // 默认为 /
    // expires, // 默认不设置,即会话级 Cookie
    // maxAge, // 默认不设置,即会话级 Cookie
    // secure, // 默认,根据请求协议决定是否开启。若 HTTP 请求,设置了 true,将无法写入浏览器
    // httpOnly, // 默认为 false
    // sameSite // 默认不设置,此时它的值取决于浏览器的默认值。比如 Chrome 80 之后,即使你在浏览器看到的是空的,但它的默认值为 Lax。
  })
  // 可写入多个
  res.cookie('other', '123')
  // other statements...
})
app.listen(port)


在客户端写入,只能通过 document.cookie 进行设置。注意,它只能为当前域写入 Cookie。假设你在个人网站设置:name=Frankie; Domain=github.com 是无效的。

document.cookie = 'name=Frankie; domain=*.example.com; path=/user; expires=Fri, 31 Dec 9999 23:59:59 GMT; max-age=3600; sameSite=Lax; secure; HttpOnly'

在浏览器里,通过 JS 脚本的方式,似乎无法写入 HttpOnly 的 Cookie。(待进一步验证)


这样设置太麻烦了,我们通常会封装一个方法来添加、修改、删除、检查 Cookie,请看:Cookie.js


未完待续...


目录
相关文章
|
6月前
|
存储 JavaScript 前端开发
js中session、cookie、 localStorage和SessionStorage的区别和特点
js中session、cookie、 localStorage和SessionStorage的区别和特点
|
1月前
|
存储 JavaScript 前端开发
JavaScript Cookie
JavaScript Cookie
16 0
|
3月前
|
存储 前端开发 JavaScript
揭秘!JavaScript本地存储的四大绝技:从Cookie到IndexedDB,让你的Web应用秒变数据存储高手,轻松应对各种挑战!
【8月更文挑战第4天】JavaScript为核心前端技术,提供多样本地存储方案以优化用户体验与减少服务器负载。首先,Cookie虽用于基本数据如登录状态,但受大小限制及安全性影响。接着,Web Storage中的LocalStorage持久存储不变数据,SessionStorage则限于单次会话。更进一步,IndexedDB作为全面数据库解决方案,支持复杂数据操作但使用较复杂。每种方式根据应用需求各有优势。
68 9
|
4月前
|
存储 Web App开发 JavaScript
浏览器【详解】Cookie(含Cookie的起源,属性,个数和大小限制,作用,优点,缺点,JS 的操作方法等)
浏览器【详解】Cookie(含Cookie的起源,属性,个数和大小限制,作用,优点,缺点,JS 的操作方法等)
214 0
|
4月前
|
存储 Web App开发 移动开发
js【详解】本地存储 Cookie、sessionStorage、localStorage
js【详解】本地存储 Cookie、sessionStorage、localStorage
144 0
|
5月前
|
JavaScript 前端开发 数据安全/隐私保护
JS中使用Cookie实现记住密码以及设置密码过期时间
JS中使用Cookie实现记住密码以及设置密码过期时间
100 0
|
5月前
|
存储 JavaScript 前端开发
JavaScript如何使用Cookie存值
JavaScript如何使用Cookie存值
31 0
|
6月前
|
存储 JavaScript 前端开发
JavaScript中的cookie、localStorage的区别和特点
JavaScript中的cookie、localStorage的区别和特点
74 6
|
6月前
|
存储 JavaScript 前端开发
JavaScript DOM 操作:解释一下 cookie、sessionStorage 和 localStorage 的区别。
Cookie是服务器发送至客户端的文本信息,会随每个请求发送回服务器,适合控制会话状态但可能暴露隐私。SessionStorage仅在当前会话中存储数据,关闭浏览器后清除,适合临时存储如登录状态。LocalStorage则持久保存数据,即使关闭浏览器也不会清除,适用于存储长期设置。三种方式各有侧重,应按需求选择。
45 0
|
6月前
|
存储 JavaScript 前端开发
JS中Cookie的基本使用
JS中Cookie的基本使用
下一篇
无影云桌面