【JavaScript】 HTTP Cookie 核心知识梳理与常用的封装实现

文章目录

  • 前言
  • 一、Cookie介绍
    • [1.1 Cookie基本结构](#1.1 Cookie基本结构)
    • [1.2 Cookie的生命周期](#1.2 Cookie的生命周期)
    • [1.3 Cookie的作用域](#1.3 Cookie的作用域)
    • [1.4 Cookie的安全性](#1.4 Cookie的安全性)
  • 二、JS里的Cookie操作封装
    • [2.1 设置cookie(name, value, options = {})](#2.1 设置cookie(name, value, options = {}))
    • [2.2 获取Cookie(name)](#2.2 获取Cookie(name))
    • [2.3 删除Cookie(name, options = {})](#2.3 删除Cookie(name, options = {}))
    • [2.4 是否有指定Cookie(name)](#2.4 是否有指定Cookie(name))
    • [2.5 获取所有Cookie()](#2.5 获取所有Cookie())
    • [2.6 清空所有Cookie(options = {})](#2.6 清空所有Cookie(options = {}))
    • [2.7 完整代码封装](#2.7 完整代码封装)
  • 总结

前言

HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。也可用于保存JWT令牌,实现身份鉴权。同样也可以存储特点数据,保存到浏览器上。

cookie 大致有以下几种用法

  • 会话状态管理,如登录状态。验证令牌。
  • 个性化设置,如用户自定义设置、web主题等。

在浏览器发起的 HTTP/HTTPS 请求 中,符合条件的 Cookie 会被浏览器自动携带发送到服务器。同域名、路径匹配、协议符合条件时,浏览器会自动携带 Cookie。

这也是.NET传统开发中WebFroms 和 ASP.NET MVC实现身份鉴权的方式,浏览器发起请求默认带上身份Cookie,发送到服务器,服务器实现身份信息校验。这种方式容易导致CSRF攻击。

CSRF攻击原理

CSRF(Cross - Site Request Forgery),即跨站请求伪造,是一种黑客攻击手段。它利用用户已登录的身份,在用户毫不知情的情况下,以用户的名义执行非授权操作,像转账、修改账户信息等。

用户登录受信任网站 A,并在浏览器本地生成 Cookie:此时用户在网站 A 上处于登录状态,浏览器保存了该网站的 Cookie。恶意网站 B 会向网站 A 发送请求。由于浏览器会自动携带网站 A 的 Cookie,网站 A 无法分辨该请求是用户的真实操作还是恶意请求,从而导致攻击发生。

一、Cookie介绍

1.1 Cookie基本结构

在 JavaScript 中,Document.cookie 是一个 属性,用于操作当前网页的 Cookie。可以把Document.cookie当成一个 getter(访问器) 和 setter(修改器),用于获取BOM上的Cookie值。

cookie 是一个字符串。多条 cookie 用分号和空格 (; )分隔 ,单条cookie即key=value 键值对(建议用encodeURIComponent ,decodeURIComponent 编码解码处理)

比如

javascript 复制代码
document.cookie="name=araby; gender=male";

1.2 Cookie的生命周期

Cookie 的生命周期一般分为会话期 Cookie和**持久性 Cookie **。主要取决于过期时间和有效时间这两个属性是否设置。

  • 会话期 Cookie 会在当前的会话结束之后删除。浏览器定义了"当前会话"结束的时间,一些浏览器重启时会使用会话恢复。这可能导致会话 cookie 无限延长。
  • 持久性 Cookie 在过期时间(Expires)指定的日期或有效期(Max-Age)指定的一段时间后被删除。
  • Expires (过期时间 - 绝对时间):告诉浏览器一个具体的日期和时间,到了这个时间点,Cookie 就应该被删除
  • Max-Age (有效期 - 相对时间):它告诉浏览器 Cookie 自被设置之日起,应该存活多少秒。浏览器在接收到 Cookie 时开始计时。【更推荐,类似于JS的new Date】
  • Max-Age优先级更高。Max-Age 和 Expires 同时设置的情况下,Max-Age生效

下面是会话期 Cookie 和持久性 Cookie 二者的比较

特性 会话期 Cookie 持久性 Cookie
生命周期 当前的会话结束(关闭页面/浏览器)之后删除 在过期时间(Expires)指定的日期或有效期(Max-Age)指定的一段时间后被删除或清空cookie
存储位置 浏览器内存 (RAM) 用户硬盘
关键属性 没有 Expires 或 Max-Age (或设为无效值) 必须设置有效的 Expires 或 Max-Age 属性
主要用途 维护单次访问的会话状态、临时数据 记住登录状态、用户偏好、长期跟踪、身份标识
优点 更安全(数据不持久),隐私风险低 用户体验更好(跨会话状态保持)
缺点 会话结束状态丢失 隐私和安全风险更高(数据持久存储)

1.3 Cookie的作用域

== Domain== 和 == Path== 标识定义了 Cookie 的作用域:即允许 Cookie 应该发送给哪些 URL。

在浏览器在发送 HTTP 请求时,会检查该请求的 URL 的主机名(Host)和路径(Path),然后查找所有满足以下条件的 Cookie 并附加到请求头 Cookie 中。

其中Domain指定了哪些主机可以接受 Cookie。通过 Domain 属性明确指定一个域,Cookie 会被发送给 指定的域名已经及其所有子域名。如果未指定,浏览器会默认将该 Cookie 的域设置为页面的域名。

比如Domain设置"example.com",则www.example.com,item.example.com也会被共享到指定Cookie 。

Path则是进一步在Domain的基础上进一步限制 Cookie 可被发送到哪些 URL 路径及其子路径。Path采用的是前缀匹配,也就是说如果设置。如果未指定,浏览器会默认将该 Cookie 的Path设置为页面的路径。

比如Path设置为"/home",则/home/homepage可以被共享到指定Cookie ,但是/user下的目录就没法访问。

1.4 Cookie的安全性

前面简单提到了CSRF攻击,了解到Cookie会在符合条件的情况下自动附加到请求头上。而Cookie 的SameSite属性则是允许服务器指定是否/何时通过跨站点请求发送,从而限制Cookie在跨站请求时的发送行为,保护数据安全。

  • Strict :最严格的模式,cookie仅发送到它来源的站点;
  • Lax:宽松模式,是跨站请求安全性和用户体验中的一种平衡。它允许部分跨站请求携带 Cookie,但仅限于安全的HTTP 方法(如 GET)和顶级导航(如链接跳转)。不支持AJAX,POST和iframe。
  • None:无限制:允许 所有跨站请求 携带 Cookie,但必须同时设置 Secure 属性,且仅在 HTTPS 连接中生效。

除了SameSite属性外,Cookie还有HttpOnly和Secure。设置 HttpOnly=true 后,JavaScript 无法访问该 Cookie(防 XSS 攻击)。设置 Secure=true 后,Cookie仅在 HTTPS 连接中传输。

模式 适用场景 安全建议
Strict 敏感操作(如支付、删除) 配合 HttpOnly 和 Secure
Lax 大部分 Web 应用 默认首选,平衡安全与体验
None 跨域服务(如 API、第三方登录) 必须配合 Secure,仅限 HTTPS

二、JS里的Cookie操作封装

下面分别介绍下JS里的Cookie操作封装。完整代码附在最后面。

2.1 设置cookie(name, value, options = {})

  1. 先判断cookie的name是否合规
javascript 复制代码
if (!name || /[^\w-]/.test(name)) {
    throw new Error('Cookie 名称无效,只能包含字母、数字、下划线和连字符');
}

正则字符串\w-用于表示匹配字母、数字或下划线和连字符。最后[ ^ ]表示不匹配括号内的任意一个字符。

  1. 然后拼接cookie最基本的键值对,使用encodeURIComponent编码。
javascript 复制代码
// 构建cookie字符串
let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`;
  1. 设置杂项,如过期时间,域,路径等属性
javascript 复制代码
// 过期时间,maxAge的优先级高于expires
if (options.maxAge != null) {
    cookieStr += `; max-age=${Math.max(0, Math.floor(options.maxAge))}`;
} else if (options.expires != null) {
    const expires = options.expires instanceof Date
        ? options.expires
        : new Date(Date.now() + options.expires * 1000);
    cookieStr += `; expires=${expires.toUTCString()}`;
}
// 处理其他选项
if (options.path) cookieStr += `; path=${options.path}`;
if (options.domain) cookieStr += `; domain=${options.domain}`;
if (options.secure) cookieStr += `; secure`;
if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
if (options.httpOnly) cookieStr += `; httponly`;
  1. 将cookie保存到document上
javascript 复制代码
document.cookie = cookieStr;

指定注意的是保存如果两个同名Cookie的路径域名完全一致,后保存的会覆盖前者。

2.2 获取Cookie(name)

获取cookie就简单多了,前文我们知道浏览器会自己更具cookie属性决定哪些页面的Http上下文需要附加cookie。这一步浏览器自身会处理,所以如果我们只需要通过JS尝试获取cookie。

通过(?:)设置非匹配组,找到字符串开头( ^ )的,或者是个分号加任意空白字符(;\s*)。

\^; \]\*匹配零个或多个不是分号的字符。 最后再解码返回结果。 ```javascript const encodedName = encodeURIComponent(name); const cookie = document.cookie.match( new RegExp(`(?:^|;\\s*)${encodedName}=([^;]*)`) ); return cookie ? decodeURIComponent(cookie[1]) : null; ``` ### 2.3 删除Cookie(name, options = {}) JS里删除cookie时间上是通过设置过期时间这种方式来间接使cookie失效。 ```javascript this.set(name, '', { ...options, maxAge: -1 // 立即过期 }); ``` ### 2.4 是否有指定Cookie(name) 调用封装的获取cookie方法,看看是不是为null ```javascript return this.get(name) !== null; ``` ### 2.5 获取所有Cookie() 先将将整个Cookie字符串按分号 (😉 拆分成数组; 使用reduce() 遍历数组,把每个Cookie转换成一个对象属性; 通过str.trim()去掉空格,使用split('=')将键值对拆分。 如果解析出的 name 存在,就将这个键值对添加到定义好的数据里,最后返回。 ```javascript return document.cookie.split(';').reduce((cookies, str) => { const [name, value] = str.trim().split('=').map(decodeURIComponent); return name ? { ...cookies, [name]: value } : cookies; }, {}); ``` ### 2.6 清空所有Cookie(options = {}) 先获取所有Cookie,调用封装的删除方法。 ```javascript for (const name of Object.keys(this.getAll())) { this.delete(name, options); } ``` ### 2.7 完整代码封装 ```javascript class EFCookieUtil { /** * 设置 Cookie * @param {string} name - Cookie 名称 * @param {string|number|boolean} value - Cookie 值 * @param {Object} [options] - 可选配置项 * @param {number|Date} [options.expires] - 过期时间(秒或日期对象) * @param {number} [options.maxAge] - 最大存活时间(秒,优先级高于 expires) * @param {string} [options.path] - 路径 * @param {string} [options.domain] - 域名 * @param {boolean} [options.secure] - 是否只在 HTTPS 发送 * @param {'Strict'|'Lax'|'None'} [options.sameSite] - 同站策略 * @param {boolean} [options.httpOnly=false] - 是否禁止 JavaScript 访问 */ static set(name, value, options = {}) { if (!name || /[^\w-]/.test(name)) { throw new Error('Cookie 名称无效,只能包含字母、数字、下划线和连字符'); } // 构建 cookie 字符串 let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(String(value))}`; // 优先级:maxAge > expires if (options.maxAge != null) { cookieStr += `; max-age=${Math.max(0, Math.floor(options.maxAge))}`; } else if (options.expires != null) { const expires = options.expires instanceof Date ? options.expires : new Date(Date.now() + options.expires * 1000); cookieStr += `; expires=${expires.toUTCString()}`; } // 处理其他选项 if (options.path) cookieStr += `; path=${options.path}`; if (options.domain) cookieStr += `; domain=${options.domain}`; if (options.secure) cookieStr += `; secure`; if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`; if (options.httpOnly) cookieStr += `; httponly`; document.cookie = cookieStr; } /** * 获取 Cookie 值 * @param {string} name - Cookie 名称 * @returns {string|null} - Cookie 值或 null */ static get(name) { if (!name) return null; const encodedName = encodeURIComponent(name); const cookie = document.cookie.match( new RegExp(`(?:^|;\\s*)${encodedName}=([^;]*)`) ); return cookie ? decodeURIComponent(cookie[1]) : null; } /** * 删除 Cookie * @param {string} name - Cookie 名称 * @param {Object} [options] - 需与设置时一致 * @param {string} [options.path] - 路径 * @param {string} [options.domain] - 域名 */ static delete(name, options = {}) { this.set(name, '', { ...options, maxAge: -1 // 立即过期 }); } /** * 检查 Cookie 是否存在 * @param {string} name - Cookie 名称 * @returns {boolean} - 是否存在 */ static has(name) { return this.get(name) !== null; } /** * 获取所有 Cookie * @returns {Object} - 包含所有 Cookie 的对象 */ static getAll() { return document.cookie.split(';').reduce((cookies, str) => { const [name, value] = str.trim().split('=').map(decodeURIComponent); return name ? { ...cookies, [name]: value } : cookies; }, {}); } /** * 清除所有 Cookie(需指定作用域) * @param {Object} [options] - 作用域配置 * @param {string} [options.path] - 路径 * @param {string} [options.domain] - 域名 */ static clear(options = {}) { for (const name of Object.keys(this.getAll())) { this.delete(name, options); } } } ``` ## 总结 本文分析了Cookie 的键值对结构、会话期 / 持久性区别,以及 Domain/Path 如何控制作用域。并讨论到SameSite、HttpOnly 与 Secure如何提升Cookie的安全性。最后基于原生 JS 封装 Cookie 工具类,涵盖增删改查、批量操作及参数配置。

相关推荐
从未、淡定3 小时前
HTTP 网络协议演进过程
网络·网络协议·http
涵信4 小时前
第一节 基础核心概念-TypeScript与JavaScript的核心区别
前端·javascript·typescript
小公主5 小时前
JavaScript 柯里化完全指南:闭包 + 手写 curry,一步步拆解原理
前端·javascript
TGB-Earnest7 小时前
【leetcode-合并两个有序链表】
javascript·leetcode·链表
GISer_Jing7 小时前
JWT授权token前端存储策略
前端·javascript·面试
拉不动的猪8 小时前
es6常见数组、对象中的整合与拆解
前端·javascript·面试
放逐者-保持本心,方可放逐8 小时前
webgl(three.js 与 cesium 等实例应用)之浏览器渲染应用及内存释放的关联与应用
开发语言·javascript·webgl·顶点着色器·three.js 释放·cesium 释放·片元着色器
行云流水6269 小时前
js实现输入高亮@和#后面的内容
前端·javascript·css
戒不掉的伤怀10 小时前
react实现axios 的简单封装
javascript·react.js·ecmascript
夏梦春蝉10 小时前
ES6从入门到精通:变量
前端·javascript·es6