【网络基础-04】Cookie+Session+Token

4 阅读11分钟

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
HttpOnlyJS 不能通过 document.cookie 读取,防 XSS 窃取HttpOnly
SameSite=...跨站是否携带,防 CSRFSameSite=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 的安全风险

  1. XSS 窃取:若站点有 XSS 漏洞,攻击者可执行 document.cookie 偷走未设 HttpOnly 的 Cookie。防御:重要 Cookie 加 HttpOnly
  2. CSRF 利用:浏览器自动带 Cookie 的特性被滥用,攻击者诱导用户发跨站请求。防御:SameSite + CSRF Token
  3. Cookie 注入/篡改:若服务器信任未签名的 Cookie 内容,攻击者可篡改 Cookie 值(如把 role=user 改成 role=admin)。防御:Cookie 内容只存 ID,敏感数据存服务端;或对 Cookie 签名
  4. 网络嗅探: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,但二者是不同层面的东西:

对比项CookieSession
存储位置客户端(浏览器)服务器端(内存/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 的安全问题

  1. 会话固定(Session Fixation) :攻击者先拿到一个 session Id(或强制让用户用指定 sessionId 登录),用户登录后 sessionId 不变,攻击者凭该 sessionId 冒充用户。防御:登录成功后立即 regenerate session Id(生成新 ID,作废旧 ID);
  2. 会话劫持:攻击者通过 XSS、嗅探、中间人等手段窃取 sessionId,冒充用户。防御:HttpOnly + HTTPS + Secure
  3. 会话不过期:长期不登出的 Session 被窃取后可永久冒充。防御:设置合理过期时间,闲置自动登出,关键操作重新认证
  4. 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. HeaderJSONBase64URL 编码 → header_b64
2. PayloadJSONBase64URL 编码 → 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 的区别

对比项SessionJWT
状态有状态(服务器存 Session)无状态(服务器不存)
存储服务器内存/Redis客户端
扩展性需共享 Session 存储天然支持分布式
吊销删 Session 即可立即失效难(需维护黑名单)
安全性sessionId 不含业务数据Payload 可被读取(勿放敏感信息)
大小sessionId 短JWT 较长(含 Header+Payload+签名)
续期服务器直接更新过期时间需重新签发或用 Refresh Token

3.5 JWT 的安全问题

  1. alg=none 漏洞:早期 JWT 库允许 alg: "none"(不签名),攻击者把 Header 改成 {"alg":"none"} 并清空 Signature,服务器若没校验算法就放行。防御:服务端强制指定算法,拒绝 none
  2. 密钥泄露:HS256 的 secret 泄露后,攻击者可伪造任意 Token。防御:密钥足够复杂、定期轮换;或用 RS256(非对称,私钥签名公钥验签,公钥可公开)
  3. 敏感信息泄露:Payload 只是 Base64 编码,任何人都能解码。把密码、手机号放进去等于明文泄露。防御:Payload 只放 userId、角色等非敏感信息
  4. 算法混淆攻击:若服务端配置用 RS256(非对称),但代码里 verify(token, publicKey) 误用公钥当 HMAC 密钥,攻击者可把 Header 改成 HS256,用公钥当 secret 伪造 Token。防御:校验 Header 的 alg 与预期一致
  5. 无法主动吊销: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 三者对比

对比项CookieSessionToken(JWT)
存储位置客户端服务端客户端
是否含业务数据可含不含(只存 ID)可含(但可解码)
服务端状态无需需要无需
跨域支持受限受限
移动端适用一般一般
吊销改客户端删服务端记录难(需黑名单)
典型场景偏好设置传统 Web 登录API/微服务/移动端

实际项目中常组合使用:浏览器端用 Cookie 传 sessionId(Session 方案),移动端用 JWT,二者可共存。


四、会话管理最佳实践

  1. Cookie 必加三件套HttpOnly(防 XSS 窃取)+ Secure(仅 HTTPS)+ SameSite=Lax/Strict(防 CSRF);
  2. 登录后 regenerate sessionId:防 Session Fixation;
  3. 设置合理过期时间:Session 闲置 30 分钟自动登出;JWT 短期(如 15 分钟)+ Refresh Token(如 7 天);
  4. 关键操作二次验证:改密码、转账等用短信/OTP/二次密码确认;
  5. JWT 密钥保护:用 RS256(非对称),私钥严格保密;HS256 的 secret 足够长且随机;
  6. JWT 服务端校验算法:拒绝 none,校验 alg 与预期一致;
  7. Cookie 内容只存 ID:敏感数据存服务端,勿在 Cookie 里存角色、余额等可被篡改的字段;
  8. 全程 HTTPS:防嗅探、防中间人;
  9. 登出要彻底:服务端删除 Session,客户端清除 Cookie/Token;
  10. 监控异常会话:IP 突变、UA 突变、短时间多地登录等触发风控。