1.16 Cookie 和 Session
在 Node.js 中,Cookie 和 Session 是实现用户身份验证和状态管理的核心技术。
一、核心概念
1. Cookie
- 定义:服务器发送到用户浏览器并保存在本地的小段数据。
- 用途:存储用户信息、跟踪会话、记住登录状态等。
- 特点:
- 数据存储在客户端。
- 随每次 HTTP 请求发送到服务器。
- 有大小限制(通常 4KB)。
- 可设置过期时间。
2. Session
- 定义:服务器端存储用户会话数据的机制。
- 用途:存储敏感信息(如用户 ID、权限),避免客户端篡改。
- 特点:
- 数据存储在服务器端。
- 每个会话有唯一的 Session ID(通常存储在 Cookie 中)。
- 需要配合 Cookie 或 URL 参数使用。
二、Cookie 基础用法
原生 Node.js 处理 Cookie
当你访问一个网站时,服务器可以通过 HTTP 响应头 Set-Cookie 向你的浏览器发送一个或多个 Cookie。浏览器会保存这些 Cookie,并在后续对该域名的请求中通过 Cookie 请求头自动发送回去给服务器。
const http = require('http');const server = http.createServer((req, res) => {// 设置一个名为 'example' 的 Cookieres.setHeader('Set-Cookie', ['example=value']);// 读取 Cookieconst cookies = req.headers.cookie;console.log('Cookies received:', cookies);res.end('Hello World');
});server.listen(3000, () => {console.log('Server running at http://localhost:3000/');
});
Cookie 的基本组成部分
一个完整的 Cookie 由以下部分组成:
name=value; Path=path; Domain=domain; Expires=date; Max-Age=seconds; HttpOnly; Secure; SameSite=mode
Set-Cookie: name=value; [属性1=值1]; [属性2=值2]; ...
1. 必选部分
name=value
:
Cookie 的名称和值,必须用=
连接,且不能包含分号、逗号或空格。- 示例:
sessionId=abc123
username=john_doe
- 示例:
2. 可选属性
Cookie 可以包含多个可选属性,用于控制 Cookie 的行为:
-
Path=/
:
指定 Cookie 的路径,限制 Cookie 只在该路径下有效。- 示例:
Path=/api
→ 仅/api
及其子路径可访问。
- 示例:
-
Domain=example.com
:
指定 Cookie 的域名,限制 Cookie 只在该域名及其子域名下有效。- 示例:
Domain=.example.com
→ 对example.com
和所有子域名有效。
- 示例:
-
Expires=Thu, 01 Jan 2030 00:00:00 GMT
:
指定 Cookie 的过期时间,之后 Cookie 将被浏览器删除。- 示例:
Expires=Wed, 21 Oct 2025 07:28:00 GMT
- 示例:
-
Max-Age=3600
:
指定 Cookie 的有效期(秒),优先于Expires
。- 示例:
Max-Age=86400
→ 有效期 24 小时。
- 示例:
-
HttpOnly
:
标记 Cookie 只能通过 HTTP (S) 请求访问,不能被 JavaScript 读取,防止 XSS 攻击。- 示例:
HttpOnly
- 示例:
-
Secure
:
标记 Cookie 只能通过 HTTPS 协议发送,防止中间人攻击。- 示例:
Secure
- 示例:
-
SameSite=Strict|Lax|None
:
控制 Cookie 在跨站请求时的行为,防止 CSRF 攻击。- 示例:
SameSite=Strict
→ 仅允许同源请求携带 Cookie。
- 示例:
Cookie 属性详解
1. Path
属性
- 作用:限制 Cookie 可被访问的路径。
- 示例:
Set-Cookie: user=john; Path=/admin
- 该 Cookie 仅在
/admin
及其子路径(如/admin/dashboard
)中有效。 - 访问
/home
时不会发送该 Cookie。
- 该 Cookie 仅在
2. Domain
属性
- 作用:指定 Cookie 可被访问的域名。
- 示例:
Set-Cookie: session=abc; Domain=.example.com
- 该 Cookie 对
example.com
、www.example.com
、api.example.com
等子域名均有效。 - 若未指定
Domain
,则默认为当前域名(不包含子域名)。
- 该 Cookie 对
3. Expires
和 Max-Age
-
Expires
:- 指定具体的过期日期(GMT 格式)。
- 示例:
Expires=Sun, 10 Jun 2025 12:00:00 GMT
→ Cookie 将在该时间过期。
-
Max-Age
:- 指定 Cookie 的有效期(秒)。
- 示例:
Max-Age=0
→ 立即删除 Cookie。
Max-Age=3600
→ 有效期 1 小时。
4. HttpOnly
- 作用:防止 JavaScript 通过
document.cookie
访问 Cookie,降低 XSS 攻击风险。 - 示例:
Set-Cookie: token=123; HttpOnly
- 该 Cookie 无法被 JavaScript 读取,只能通过 HTTP 请求发送。
5. Secure
- 作用:确保 Cookie 仅在 HTTPS 连接中发送,防止中间人攻击。
- 示例:
Set-Cookie: auth=secret; Secure
- 在 HTTP 连接中,该 Cookie 不会被发送。
6. SameSite
- 作用:控制 Cookie 在跨站请求时的行为,防止 CSRF 攻击。
- 取值:
-
Strict
:
仅允许同源请求携带 Cookie。
示例:Set-Cookie: csrf_token=abc; SameSite=Strict
- 从
https://example.com
访问https://example.com
时发送 Cookie。 - 从
https://another-site.com
访问https://example.com
时不发送 Cookie。
- 从
-
Lax
:
允许部分跨站请求携带 Cookie(主要是安全的 GET 请求)。
示例:Set-Cookie: session=xyz; SameSite=Lax
- 从外部网站通过链接访问(GET 请求)时发送 Cookie。
- 从外部网站通过表单提交(POST 请求)时不发送 Cookie。
-
None
:
允许跨站请求携带 Cookie,但必须同时设置Secure
属性。
示例:Set-Cookie: tracking_id=123; SameSite=None; Secure
- 仅在 HTTPS 环境下允许跨站请求携带 Cookie。
-
服务器发送 Cookie报文
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly
Set-Cookie: user=john; Expires=Thu, 01 Jan 2030 00:00:00 GMT; Secure; SameSite=Lax
Content-Type: text/html
客户端发送 Cookie报文
GET /api/data HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; user=john
-
Cookie 大小限制:
- 大多数浏览器限制单个 Cookie 大小为 4KB。
- 每个域名的 Cookie 数量限制通常为 50 个左右。
-
跨域 Cookie:
- 默认情况下,Cookie 不支持跨域。
- 可通过
Domain
和SameSite=None; Secure
实现有限的跨域支持。
-
编码问题:
- Cookie 值应使用
encodeURIComponent()
编码,读取时使用decodeURIComponent()
解码。
- Cookie 值应使用
在 Express 中设置 Cookie
需要的中间件
npm install cookie-parser
const express = require('express');
const app = express();const cookieParser = require('cookie-parser');
app.use(cookieParser()); // 解析 Cookie// 设置 Cookie
app.get('/set-cookie', (req, res) => {res.cookie('username', 'john_doe', {maxAge: 3600000, // 有效期 1 小时(毫秒)httpOnly: true, // 防止客户端脚本访问secure: process.env.NODE_ENV === 'production', // 仅 HTTPSsameSite: 'strict' // 防止 CSRF 攻击});res.send('Cookie 已设置');
});// 读取 Cookie
app.get('/get-cookie', (req, res) => {const username = req.cookies.username;res.send(`用户名: ${username}`);
});// 清除 Cookie
app.get('/clear-cookie', (req, res) => {res.clearCookie('username');res.send('Cookie 已清除');
});app.listen(3000);
三、Session 基础用法
在 Node.js 中,没有内置的 Session 模块,测试环境.可以通过组合 HTTP 模块、Cookie 和内存来原生实现 Session 管理。
用户需要session时.首先解析cookie.如果cookie里包含Session ID.,通过 Session ID 查找对应的数据。如果没有Session ID,生成一个唯一的 Session ID(通常使用 UUID),服务器端维护一个 Session 数据存储,将 Session ID 存储在 Cookie 中,随请求写会客户端
// 内存中的 Session 存储
const sessions = new Map();
const sessionCookieName = 'session_id';const crypto = require('crypto');// 生成随机 Session ID
function generateSessionId() {return crypto.randomBytes(16).toString('hex');
}// 解析 Cookie 字符串
function parseCookies(cookieHeader) {const cookies = {};if (cookieHeader) {cookieHeader.split('; ').forEach(cookie => {const [name, value] = cookie.split('=');cookies[name] = decodeURIComponent(value);});}return cookies;}setInterval(() => {for (const [sessionId, session] of sessions) {if (Date.now() - session.timestamp > 30 * 60 * 1000) {// 清除过期的 Sessionsessions.delete(sessionId);}}}, 30*60*1000); // 每30分钟检查一次session是否过期function destroySession(session_id){sessions.delete(session_id);}module.exports = function(req, res){//用nodejs 解析res带过来的cookieconst cookie = req.headers.cookie;const cookieObj = parseCookies(cookie);const sessionId = cookieObj[sessionCookieName];let timestamp = Date.now();let state = 0; // state =0 刚创建,1,过期,从新创建,2,获取if( sessionId && sessions.has(sessionId)){let tmpObj = sessions.get(sessionId)if(timestamp - tmpObj["timestamp"] > 30*60*1000){//session过期state = 1;let obj = {timestamp,state};sessions.set(sessionId,obj);return obj;}tmpObj["state"] = 2;tmpObj["timestamp"] = timestamp;req.destroySession = destroySession.bind(null,sessionId)return tmpObj;}else{const newSessionId = generateSessionId();let obj = {timestamp,state};sessions.set(newSessionId,obj)res.setHeader('Set-Cookie', [sessionCookieName + '=' + newSessionId]);req.destroySession = destroySession.bind(null,newSessionId)return obj;}}
在 Express 中使用 Session
express-session
是 Express.js 的官方会话中间件,用于管理用户会话。它提供了简单易用的 API,支持多种存储后端,是构建 Node.js 会话系统的首选方案。
npm install express-session
const express = require('express');
const session = require('express-session');
const app = express();// 配置 Session
app.use(session({secret: 'your-secret-key', // 用于签名 Session IDresave: false, // 不强制保存未修改的 SessionsaveUninitialized: true, // 保存新创建但未修改的 Sessioncookie: {maxAge: 3600000, // 有效期 1 小时httpOnly: true,secure: process.env.NODE_ENV === 'production'}
}));// 使用 Session
app.get('/login', (req, res) => {// 模拟登录req.session.user = { id: 1, name: 'John' };req.session.isLoggedIn = true;res.send('登录成功');
});app.get('/profile', (req, res) => {if (req.session.isLoggedIn) {res.send(`欢迎 ${req.session.user.name}`);} else {res.status(401).send('未登录');}
});app.get('/logout', (req, res) => {req.session.destroy(err => {if (err) {console.error(err);}res.send('已注销');});
});app.listen(3000);
-
工作流程:
- 用户登录时,服务器创建 Session 并生成唯一 Session ID。
- Session ID 通过 Cookie 发送到客户端。
- 后续请求中,客户端通过 Cookie 发送 Session ID。
- 服务器通过 Session ID 查找对应的 Session 数据。
与JWT对比
特性 | Cookie/Session | JWT |
---|---|---|
存储位置 | 服务器(Session)+ 客户端(Cookie) | 客户端全部存储 |
状态性 | 有状态(服务器存储数据) | 无状态(数据编码在 Token 中) |
安全性 | 较高(敏感数据在服务器) | 中等(Token 可能被篡改) |
跨域支持 | 差(需特殊配置) | 好(Token 可放在 Header 中) |
性能 | 中等(需查询服务) | 高(无需查询服务器) |
失效控制 | 容易(删除 Session) | 困难(需实现黑名单) |