什么是 cookie ?
| 模块 | 定义 |
|---|---|
| 理解 | Cookie 是一小块 数据(服务端发送给浏览器)。浏览器存储 Cookie 用于与服务端之间传输数据。 |
| 作用 | 保持会话(登录状态)-个性化设置(主题)-浏览器行为追踪(分析用户行为) |
| 种类 | 会话(浏览器关闭就删除)、持久(登录状态等)、第一方(本网站)/第三方(其他域名)、安全(HTTPS、httpOnly、SmaeSite Cookie) |
如何使用 Cookie
注意: Cookie 使用要安全,通常需要 加密。
cookie 设置属性
| 名称 | 类型 | 描述 |
|---|---|---|
| Name | String | Cookie 的名称,用于标识 Cookie。 |
| Value | String | Cookie 的值,包含该 Cookie 的具体信息。 |
| Expires/Max-Age | Date/Number | Cookie 的过期时间,可以通过设置 Expires 或者 Max-Age 属性来指定。如果不设置,那么该 Cookie 将在浏览器关闭时自动删除。 |
| Domain | String | Cookie 所属的域名。如果不指定,那么该 Cookie 将仅适用于设置它的域名。 |
| Path | String | Cookie 所在的路径。如果不指定,那么该 Cookie 将仅适用于设置它的路径。 |
| Secure | Boolean | 一个布尔值,表示是否仅在使用 HTTPS 协议时发送 Cookie。如果设置为 true,则该 Cookie 仅适用于 HTTPS 协议。 |
| HttpOnly | Boolean | 一个布尔值,表示是否允许客户端通过 JavaScript 访问该 Cookie。如果设置为 true,则该 Cookie 仅适用于 HTTP 协议,并且无法通过 JavaScript 访问。 |
浏览器读取 Cookie 和解析 Cookie
览器提供了一个名为 document.cookie 的 API,可以用于读取和设置 Cookie。
const cookies = document.cookie.split("; ");
cookies.forEach((cookie) => {
const [name, value] = cookie.split("=");
console.log(`${name}=${value}`);
});
以 Node.js 为例子使用 Cookie
import http from 'node:http'
const server = http.createServer((req, res) => { {
// 在响应头中设置 Cookie
const server = http.createServer((req, res) => { // 服务器响应请求的逻辑代码 });
// 从请求头中读取 Cookie:
const cookie = req.headers.cookie;
// ...
});
server.listen(3000, () => { console.log('Server is running on port 3000'); });
当然如果你使用 express 等框架会提供更加简单的操作 cookie 方式:cookie-parser。
cookie 的限制
| 限制/属性 | 描述 | 格式/可选值 |
|---|---|---|
| 大小限制 | 每个 Cookie 的大小 | 4KB 以内 |
| 数量限制 | 每个域名下 Cookie 的数量 | 50 个以内 |
| 安全限制 | Secure | true/false |
| 安全限制 | HttpOnly | true/false |
| 有效期限制 | Expires | 格式:Wdy, DD-Mon-YYYY HH:MM:SS GMT |
| 有效期限制 | Max-Age | 单位:秒 |
| 路径限制 | Path | Cookie 的路径 |
cookie 应用场景
| 应用场景 | 描述 |
|---|---|
| 身份验证 | 通过在用户登录时创建 Cookie 来标识用户身份,使用户可以在会话期间保持登录状态。 |
| 记住密码 | 创建一个包含用户名和密码的 Cookie,使得用户可以在下一次登录时免输入账号密码。 |
| 记录用户偏好 | 在 Cookie 中存储用户的偏好设置,例如语言、主题、字体大小等,以便用户下次访问时恢复相应的设置。 |
| 购物车 | 在 Cookie 中存储用户加入购物车的商品信息,以便用户可以在下次访问时恢复购物车状态。 |
| 个性化推荐 | 根据用户的历史浏览记录和偏好设置,在 Cookie 中存储相关数据,以便进行个性化推荐。 |
| 统计分析 | 在 Cookie 中存储用户行为数据,例如页面访问次数、停留时间等,以便进行统计分析和用户行为分析。 |
基于 JS 封装 Cookie 类
class MyCookie {
static set(name, value, options = {}) {
const { expires, path, domain, secure } = options;
document.cookie =
`${name}=${encodeURIComponent(value)}` +
(expires ? `; expires=${expires.toUTCString()}` : "") +
(path ? `; path=${path}` : "") +
(domain ? `; domain=${domain}` : "") +
(secure ? "; secure" : "");
}
static get(name) {
const cookieArr = document.cookie.split("; ");
for (let i = 0; i < cookieArr.length; i++) {
const [cookieName, cookieValue] = cookieArr[i].split("=");
if (cookieName === name) {
return decodeURIComponent(cookieValue);
}
}
return null;
}
static delete(name, options = {}) {
const { path, domain } = options;
document.cookie =
`${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC` +
(path ? `; path=${path}` : "") +
(domain ? `; domain=${domain}` : "");
}
}
// 测试用例示例
describe("Cookie", () => {
// 测试设置 Cookie
it("应该使用给定的名称、值和选项设置一个 Cookie", () => {
Cookie.set("test", "123", {
expires: new Date(Date.now() + 86400000),
path: "/",
domain: "example.com",
secure: true,
});
const cookie = document.cookie;
expect(cookie).toMatch("test=123");
expect(cookie).toMatch("expires");
expect(cookie).toMatch("path=/");
expect(cookie).toMatch("domain=example.com");
expect(cookie).toMatch("secure");
});
// 测试获取 Cookie
it("应该返回给定名称的 Cookie 的值", () => {
document.cookie = "test=123";
const value = Cookie.get("test");
expect(value).toBe("123");
});
// 测试删除 Cookie
it("应该删除具有给定名称和选项的 Cookie", () => {
Cookie.delete("test", {
path: "/",
domain: "example.com",
});
const cookie = document.cookie;
expect(cookie).not.toMatch("test=123");
});
});
Remix 中如何处理 Cookie
Remix 中对 Cookie 进行了封装, Remix 提供了 Cookie API
api 和属性
api 导入
import { isCookie, createCookie } from "@remix-run/node";
属性
const cookie = createCookie();
cookie.name; // 设置 Cookie 的名字
cookie.isSinged; // 是否使用了 secrets
cookie.expires; // 过期时间
创建 createCookie
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
maxAge: 604_800, // one week
});
// 加密
const cookie = createCookie("user-prefs", {
secrets: ["s3cret1"],
});
userPrefs 具有: parse 解析/serialize 序列化, 配合 Remix loader/action 来使用
import { userPrefs } from "~/cookies";
// loader/action
const cookieHeader = request.headers.get("Cookie");
const cookie = (await userPrefs.parse(cookieHeader)) || {};
// 序列化
redirect("/", {
headers: {
"Set-Cookie": await userPrefs.serialize(cookie),
})
判断是否 isCookie
import { isCookie } from "@remix-run/node";
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie)); // true
Remix 官方使用示例
- 设置 cookie
import { createCookie } from "@remix-run/node";
export const gdprConsent = createCookie("gdpr-consent", {
maxAge: 31536000, // One Year
});
- 处理 cookie
import type { ActionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { gdprConsent } from "~/cookies";
export const action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
const cookieHeader = request.headers.get("Cookie");
const cookie = (await gdprConsent.parse(cookieHeader)) || {};
if (formData.get("accept-gdpr") === "true") {
cookie.gdprConsent = true;
}
return json(
{ success: true },
{
headers: {
"Set-Cookie": await gdprConsent.serialize(cookie),
},
}
);
};
如果仅仅使用 cookie 完成登录
优点:
- 不需要频繁输入账号密码,直接通过 cookie 传输
缺点:
- 不安全,容易被篡改
- 如果多页面,需要频繁读取 cookie 造成各种风险
基于 Cookie 的缺点,这里我们就需要引出 Session 绘画来弥补 Cookie 在登录等业务上的缺点。
Session
Session 是一个在 Web 应用中跟踪用户会话状态的机制。当用户第一次访问 Web 应用时,服务器会创建一个唯一的 Session ID,并将其存储在 Cookie 中返回给客户端。
Cookie-Session 作为一种 最简单 的鉴权方式, 广泛被使用。
Session 的主要作用
在服务器端存储用户的状态信息,这些信息可以是任何类型的数据,例如登录信息、购物车内容、用户配置等。
Session 的生命周期
| 阶段 | 描述 |
|---|---|
| 创建阶段 | 当用户第一次访问网站时,服务器会创建一个唯一的 Session ID,并将该 ID 存储在 Cookie 中返回给客户端。 |
| 活动阶段 | 在用户会话期间,客户端的每个请求都会带上 Cookie 中存储的 Session ID。服务器使用 Session ID 来获取对应的 Session 数据,以此来保持用户状态和跟踪用户活动。 |
| 过期阶段 | 一般情况下,Session 有一个过期时间,如果用户在一段时间内没有活动,则服务器会删除该 Session 数据,以释放资源。 |
Session 的存储方式
| Session 的存储方式 | 说明 |
|---|---|
| Cookie-based | 将 Session ID 存储在客户端浏览器的 Cookie 中,每次请求时携带 Cookie 进行验证 |
| Server-side | 将 Session ID 及其对应的数据存储在服务器端,客户端请求时携带 Session ID 进行验证 |
Session 缺点
- 有被劫持 Session ID 的可能,对 Session ID 的强壮性加强,也可以通过定期更换和有效的加密方式的等方式处理
- Session 与设备之间存在一对多,和多对一的关系,所以共享和同步的问题需要正确的处理。
Session 应用场景
| Session 在 Web 应用中的应用 | 描述 |
|---|---|
| 用户认证和授权 | Session 可以存储用户登录状态和权限信息,实现用户认证和授权 |
| 购物车和在线支付 | Session 可以存储用户的购物车信息和支付状态,实现购物车和在线支付等功能 |
| 分布式应用的集群部署 | Session 需要实现共享和同步,以保证多个应用实例之间的数据一致性 |
| Session 劫持和防范 | Session 可能被黑客攻击和劫持,需要采取相应的安全措施 |
| Session 共享和同步 | 分布式应用部署时,需要实现 Session 共享和同步,以保证多个应用实例之间的数据一致性 |
| Session 数据加密和保护 | 为了提高数据的安全性,需要对 Session 数据进行加密和保护 |
Session 优化
| Session 的优化和性能 | 描述 |
|---|---|
| 存储和读取优化 | 使用内存数据库如 Redis、Memcached 存储 Session 数据,以减少数据库的读写次数,并对 Session 数据进行序列化和反序列化 |
| 过期时间和清理策略 | 根据业务需求和系统性能进行调整,例如根据用户活跃度、Session 数据量和服务器负载等进行设置 |
| 缓存和 Session 结合应用 | 使用分布式缓存如 Redis、Memcached 存储 Session 数据和缓存页面数据,以提高系统的响应速度和并发访问量 |
Remix 中 session
| 功能 | 方法名 | 描述 |
|---|---|---|
| 创建 session 对象 | createSession | 创建一个 session 对象 |
| 创建 session 存储库 | createSessionStorage | 可以方便地将 session 数据存储到外部数据库,而不是本机内存中 |
| 创建内存 session 存储库 | createMemorySessionStorage | 将所有的 session 数据保存在服务器内存中,速度较快但不够安全 |
| 创建基于文件的 session 存储库 | createFileSessionStorage | 可以将 session 数据保存到文件中,相对于内存存储更加稳定和安全,但速度较慢 |
目前 Remix 也支持了 Cloudflare Workder 和 Amazon Session 存储。
Remix 中 Session 设计与 Cookie 类似,使用方法也类似:
import { isSession, createSession } from "@remix-run/node";
// 定义 session 数据,创建 session 对象
const sessionData = { foo: "bar" };
const session = createSession(sessionData, "remix-session");
console.log(isSession(session));
session.has;
session.set;
session.flash; // `session.flash` 是一种在 Web 应用中实现消息提示的机制。
session.get;
session.unset;
在 Loader 和 Action 中使用 Session
import { commitSession, getSession } from "../sessions";
export async function action({ params, request }: ActionArgs) {
// 从 getSession 和 Cookie 中获取 session
const session = await getSession(request.headers.get("Cookie"));
const deletedProject = await archiveProject(params.projectId);
// 设置首次读取时将取消设置的会话值
session.flash(
"globalMessage",
`Project ${deletedProject.name} successfully archived`
);
return redirect("/dashboard", {
headers: {
"Set-Cookie": await commitSession(session), // 提交 session
},
});
}
session 示例
小结
本文主要回顾了 cookie/session 的基础,然后使用了 Remix 中 cookie/session 的用法优缺点,是使用 createCookie 函数创建 cookie, 以及属性和方法, 使用各种不的 session 创建方法并创建不同的 session 和其优缺点以及 Remix 官方创建的示例。
微信搜索并关注公踪号 进二开物,更多技术 JS/TS/CSS/Rust 文章...