Cookie-Session-Token
HTTP 是无状态的,服务器记不住「你是谁」。要实现登录、购物车、个性化推荐,必须借助「会话管理」机制。当前WEB有三种主流的身份维持方案—— Cookie、Session、Token(JWT),了解他们的工作原理、属性配置、是安全防御基本知识。
一、Cookie
1.1 什么是 Cookie、为什么需要 Cookie
HTTP 是无状态的——服务器不记得上一次是谁请求的,每个请求独立。这简化了协议设计,但带来一个问题:「登录后第二次请求,服务器怎么知道还是我?」Cookie 就是为了解决这个问题而诞生的。
Cookie 是服务器通过响应头 Set-Cookie 下发给浏览器的小段数据(一般不超过 4KB),浏览器会保存下来,后续请求同一域名时自动带上。就像餐厅给你办了一张会员卡,下次来你主动出示,餐厅就知道你是谁。
1.2 Cookie 的工作流程
① 首次请求(无 Cookie)
客户端 ──────────────────────→ 服务器
GET /login
② 服务器响应,下发 Cookie
客户端 ←────────────────────── 服务器
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; HttpOnly; Path=/
③ 后续请求(自动携带 Cookie)
客户端 ──────────────────────→ 服务器
GET /profile
Cookie: sessionid=abc123 ← 浏览器自动带上
④ 服务器读取 Cookie,识别用户身份
关键点:浏览器会在满足 Domain/Path 匹配的所有请求中自动携带 Cookie,无需 JS 介入。这也带来安全风险(CSRF 的根源)。
1.3 Cookie 的核心属性详解
一个完整的 Set-Cookie 头如下:
Set-Cookie: sessionid=abc123; Domain=.example.com; Path=/; Expires=Wed, 09 Jun 2026 10:00:00 GMT; Secure; HttpOnly; SameSite=Lax
| 属性 | 作用 | 示例 |
|---|---|---|
Name=Value | 键值对,必填 | sessionid=abc123 |
Expires=日期 | 绝对过期时间,不设则为「会话 Cookie」(浏览器关闭即清除) | Expires=Wed, 09 Jun 2026 10:00:00 GMT |
Max-Age=秒 | 相对有效期(优先于 Expires) | Max-Age=3600(1小时后过期) |
Domain=域 | 作用域名,默认当前域;带前导 . 表示包含子域 | Domain=.example.com(a.example.com 也带) |
Path=路径 | 作用路径,该路径下才携带 | Path=/(全站) |
Secure | 仅 HTTPS 下才发送 | Secure |
HttpOnly | JS 不能通过 document.cookie 读取,防 XSS 窃取 | HttpOnly |
SameSite=... | 跨站是否携带,防 CSRF | SameSite=Lax |
1.4 SameSite 的三种值与 CSRF 防御
SameSite 是现代浏览器防 CSRF 的关键属性:
- Strict:完全禁止跨站携带。即使用户从别的站点点了链接过来,也不会带 Cookie。最安全,但用户体验差(从 Google 搜索结果点进已登录站点,仍要重新登录);
- Lax(多数浏览器默认):顶层导航(地址栏跳转、
<a>链接、GET 请求)会带 Cookie,但 POST、iframe、AJAX、img 等不带。平衡安全与体验,能防大部分 CSRF; - None:允许跨站携带,但必须配合 Secure(仅 HTTPS)。需显式声明
SameSite=None; Secure。
CSRF 防御原理:CSRF 攻击者诱导用户访问恶意站点,恶意站点用 <img src="..."> 或表单向目标站发跨站请求。若 Cookie 设了 SameSite=Lax,POST 请求不会带 Cookie,攻击者就无法冒充用户。
1.5 Cookie 的安全风险
- XSS 窃取:若站点有 XSS 漏洞,攻击者可执行
document.cookie偷走未设 HttpOnly 的 Cookie。防御:重要 Cookie 加 HttpOnly; - CSRF 利用:浏览器自动带 Cookie 的特性被滥用,攻击者诱导用户发跨站请求。防御:SameSite + CSRF Token;
- Cookie 注入/篡改:若服务器信任未签名的 Cookie 内容,攻击者可篡改 Cookie 值(如把
role=user改成role=admin)。防御:Cookie 内容只存 ID,敏感数据存服务端;或对 Cookie 签名; - 网络嗅探:HTTP 下 Cookie 明文传输,可被中间人抓取。防御:全程 HTTPS + Secure 属性。
二、Session
2.1 什么是 Session
Cookie 把数据存在客户端,有泄露风险。Session 是服务器端保存的会话状态——用户登录后,服务器在内存/Redis 里创建一条 Session 记录,生成一个唯一的 sessionId,通过 Set-Cookie 下发浏览器。后续请求浏览器带 sessionId,服务器查 Session 库识别用户。
类比:Cookie 像把会员信息写在卡上随身带(可能被偷看篡改);Session 像服务器存档案,只给你一张写着编号的取件条(sessionId),你出示取件条,服务器查档案。敏感数据在服务器手里,更安全。
2.2 Session 与 Cookie 的关系
Session 通常依赖 Cookie 传递 sessionId,但二者是不同层面的东西:
| 对比项 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端(内存/Redis) |
| 安全性 | 较低(客户端可改) | 较高(服务器控制) |
| 大小限制 | 单个约 4KB | 无限制(服务器资源决定) |
| 有效期 | 可设长期 | 通常短期,闲置自动过期 |
| 依赖关系 | 独立 | 通常依赖 Cookie 传 sessionId |
Session 也可走 URL 重写(把 sessionId 拼在 URL 里,如 /profile;jsessionid=abc123),但会泄露在日志、Referer 里,不推荐。
2.3 Session 的工作流程
① 用户登录(提交账号密码)
客户端 ──POST /login {user, pass}──→ 服务器
② 服务器验证通过,创建 Session
服务器内存/Redis: { sessionId=abc123, userId=42, role=admin, expire=... }
服务器响应: Set-Cookie: sessionId=abc123; HttpOnly
③ 后续请求
客户端 ──GET /profile, Cookie: sessionId=abc123──→ 服务器
④ 服务器根据 sessionId 查 Session 库,识别 userId=42
2.4 Session 的安全问题
- 会话固定(Session Fixation) :攻击者先拿到一个 session Id(或强制让用户用指定 sessionId 登录),用户登录后 sessionId 不变,攻击者凭该 sessionId 冒充用户。防御:登录成功后立即
regeneratesession Id(生成新 ID,作废旧 ID); - 会话劫持:攻击者通过 XSS、嗅探、中间人等手段窃取 sessionId,冒充用户。防御:HttpOnly + HTTPS + Secure;
- 会话不过期:长期不登出的 Session 被窃取后可永久冒充。防御:设置合理过期时间,闲置自动登出,关键操作重新认证;
- Session 预测:若 sessionId 生成可预测(如自增 ID),攻击者可猜。防御:用密码学安全的随机数生成 sessionId。
2.5 Session 的扩展性问题
服务器要存大量 Session,水平扩展困难(多台服务器 Session 不共享)。解决方案:
- 粘性会话(Sticky Session):负载均衡按 IP/cookie 把同一用户固定到同一台服务器;
- Session 集中存储:把 Session 存 Redis 等共享存储,所有服务器读写同一处(主流);
- Token(见下节):无状态方案,服务器不存 Session,从根本上解决扩展问题。
三、Token(JWT)
3.1 什么是 Token、为什么需要 Token
Token 是无状态的认证方案——服务器不存会话状态,而是签发一个带签名的 Token 给客户端,客户端每次请求带上 Token,服务器验签即可识别用户。最常见的是 JWT(JSON Web Token) 。
为什么需要 Token:
- RESTful API:REST 强调无状态,Session 与之相悖;
- 移动端:App 不像浏览器自动管理 Cookie,用 Token 更灵活;
- 跨域/跨服务:微服务架构下,多服务共享 Session 麻烦,Token 自带身份信息,任何服务都能验签;
- CDN/无状态服务器:服务器无需共享 Session 存储,易水平扩展。
3.2 JWT 的三段结构
JWT 由三段以 . 分隔的字符串组成:Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTYwMH0 . SflKxwRJSMeKKF2QT4fwpXn...yJCQ
┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
Header Payload Signature
-
Header:头部,JSON 格式,说明 Token 类型和签名算法。
{"alg": "HS256", "typ": "JWT"} -
Payload:载荷,JSON 格式,存放声明(claims)。注意:只是 Base64 编码,不是加密,任何人都能解码看到内容!不要放敏感信息!
{"sub": "42", "name": "admin", "role": "admin", "exp": 1600000000, "iat": 1599000000}常见声明:
sub(用户ID)、exp(过期时间)、iat(签发时间)、iss(签发者)、aud(受众)。 -
Signature:签名,防止 Payload 被篡改。
HMACSHA256(base64url(header) + "." + base64url(payload), secret)
3.3 JWT 的编码过程
1. Header → JSON → Base64URL 编码 → header_b64
2. Payload → JSON → Base64URL 编码 → payload_b64
3. 用 secret 对 (header_b64 + "." + payload_b64) 做 HMAC-SHA256 → signature_b64
4. 拼接: header_b64 + "." + payload_b64 + "." + signature_b64
Base64URL 与普通 Base64 的区别:把 + 换成 -,/ 换成 _,去掉 = 填充,以适配 URL 传递。
验证过程:服务器收到 Token 后,取前两段重新做 HMAC 算签名,与第三段比对——一致则未被篡改,再检查 exp 是否过期。
3.4 JWT 与 Session 的区别
| 对比项 | Session | JWT |
|---|---|---|
| 状态 | 有状态(服务器存 Session) | 无状态(服务器不存) |
| 存储 | 服务器内存/Redis | 客户端 |
| 扩展性 | 需共享 Session 存储 | 天然支持分布式 |
| 吊销 | 删 Session 即可立即失效 | 难(需维护黑名单) |
| 安全性 | sessionId 不含业务数据 | Payload 可被读取(勿放敏感信息) |
| 大小 | sessionId 短 | JWT 较长(含 Header+Payload+签名) |
| 续期 | 服务器直接更新过期时间 | 需重新签发或用 Refresh Token |
3.5 JWT 的安全问题
- alg=none 漏洞:早期 JWT 库允许
alg: "none"(不签名),攻击者把 Header 改成{"alg":"none"}并清空 Signature,服务器若没校验算法就放行。防御:服务端强制指定算法,拒绝none; - 密钥泄露:HS256 的 secret 泄露后,攻击者可伪造任意 Token。防御:密钥足够复杂、定期轮换;或用 RS256(非对称,私钥签名公钥验签,公钥可公开) ;
- 敏感信息泄露:Payload 只是 Base64 编码,任何人都能解码。把密码、手机号放进去等于明文泄露。防御:Payload 只放 userId、角色等非敏感信息;
- 算法混淆攻击:若服务端配置用 RS256(非对称),但代码里
verify(token, publicKey)误用公钥当 HMAC 密钥,攻击者可把 Header 改成HS256,用公钥当 secret 伪造 Token。防御:校验 Header 的 alg 与预期一致; - 无法主动吊销:JWT 签发后到
exp前一直有效。防御:维护黑名单(违背无状态初衷)、缩短有效期、用 Refresh Token。
3.6 Refresh Token 机制
由于 JWT 无法主动吊销,业界常用 双 Token 方案:Access Token(短期)+ Refresh Token(长期)。
① 登录成功
服务器返回: Access Token(15分钟) + Refresh Token(7天)
② 正常请求带 Access Token
客户端 ──Authorization: Bearer <AccessToken>──→ 服务器
③ Access Token 过期
客户端 ──POST /refresh, {refreshToken}──→ 服务器
④ 服务器验证 Refresh Token,签发新的 Access Token
(Refresh Token 可存服务端,吊销时删除即可)
⑤ 客户端用新 Access Token 继续请求
设计要点:
- Access Token 有效期短(15~30 分钟),即使泄露损失有限;
- Refresh Token 有效期长(7~30 天),但只用于换 Access Token,不用于业务请求,且服务端可记录并吊销;
- Refresh Token 应存储于 HttpOnly Cookie(防 JS 读取),Access Token 可存内存或 localStorage(注意 XSS);
- Refresh Token 一次性使用:每次刷新后旧 Token 失效,并检测重放(若旧 Token 被用,说明可能被盗,强制全部登出)。
3.7 Cookie vs Session vs Token 三者对比
| 对比项 | Cookie | Session | Token(JWT) |
|---|---|---|---|
| 存储位置 | 客户端 | 服务端 | 客户端 |
| 是否含业务数据 | 可含 | 不含(只存 ID) | 可含(但可解码) |
| 服务端状态 | 无需 | 需要 | 无需 |
| 跨域支持 | 受限 | 受限 | 好 |
| 移动端适用 | 一般 | 一般 | 好 |
| 吊销 | 改客户端 | 删服务端记录 | 难(需黑名单) |
| 典型场景 | 偏好设置 | 传统 Web 登录 | API/微服务/移动端 |
实际项目中常组合使用:浏览器端用 Cookie 传 sessionId(Session 方案),移动端用 JWT,二者可共存。
四、会话管理最佳实践
- Cookie 必加三件套:
HttpOnly(防 XSS 窃取)+Secure(仅 HTTPS)+SameSite=Lax/Strict(防 CSRF); - 登录后 regenerate sessionId:防 Session Fixation;
- 设置合理过期时间:Session 闲置 30 分钟自动登出;JWT 短期(如 15 分钟)+ Refresh Token(如 7 天);
- 关键操作二次验证:改密码、转账等用短信/OTP/二次密码确认;
- JWT 密钥保护:用 RS256(非对称),私钥严格保密;HS256 的 secret 足够长且随机;
- JWT 服务端校验算法:拒绝
none,校验alg与预期一致; - Cookie 内容只存 ID:敏感数据存服务端,勿在 Cookie 里存角色、余额等可被篡改的字段;
- 全程 HTTPS:防嗅探、防中间人;
- 登出要彻底:服务端删除 Session,客户端清除 Cookie/Token;
- 监控异常会话:IP 突变、UA 突变、短时间多地登录等触发风控。