Session、Cookie、Token 三种认证方式详解
Session、Cookie、Token 三种认证方式详解
一、从一个场景说起
你在浏览器里登录了淘宝,关掉电脑,第二天打开浏览器,发现还是登录状态。你掏出手机扫码登录微信网页版,手机确认后,网页自动跳转。你在公司电脑上打开了"记住密码"的功能,两周后再访问,依然不需要重新登录。
这三种场景,恰好对应了 Web 认证的三种核心机制:Cookie、Session 和 Token。它们看起来都在解决"记住用户是谁"的问题,但背后的原理和适用场景截然不同。

二、Cookie —— 浏览器的小纸条
2.1 什么是 Cookie?
Cookie 是服务器让浏览器存储在用户本地的一小段数据(通常不超过 4KB)。浏览器在后续请求同一域名时,会自动把这段数据带在请求头里发给服务器。
你可以把它理解成:服务员在你手上盖了个章,你下次再来,服务员一看你的手就知道你是谁。
2.2 Cookie 的工作原理
1 | 1. 浏览器请求服务器(首次访问) |
2.3 Cookie 的关键属性
| 属性 | 作用 | 说明 |
|---|---|---|
HttpOnly |
禁止 JavaScript 访问 | 防止 XSS 攻击窃取 Cookie |
Secure |
仅 HTTPS 传输 | 防止中间人截获 |
SameSite |
控制跨站请求携带 | Strict/Lax/None,防 CSRF |
Domain |
指定生效域名 | 可跨子域共享 |
Path |
指定生效路径 | 默认当前路径 |
Max-Age / Expires |
设置过期时间 | 不设则为会话 Cookie,关闭浏览器即失效 |
2.4 代码示例
服务端设置 Cookie(Node.js Express):
1 | // 设置一个 HttpOnly + Secure 的 Cookie |
2.5 Cookie 的局限性
- 容量小:单个 Cookie 不超过 4KB,每个域名 Cookie 总数有限制
- 每次请求都携带:会增加请求体积,浪费带宽
- 跨域限制:默认不同源不共享 Cookie(虽然可以配置)
- CSRF 风险:浏览器自动携带 Cookie 的特性可能被利用
三、Session —— 服务器端的用户档案
3.1 什么是 Session?
Session 是服务器为每个用户维护的会话数据,存储在服务端。浏览器只保存一个 Session ID(通常放在 Cookie 中),服务器通过这个 ID 来查找对应的用户数据。
可以理解为:你去健身房办了个卡,卡上只有一个编号(Session ID),你的个人信息、会员等级、课时余额都存储在健身房的系统里(服务端 Session)。
3.2 Session 认证流程
1 | 用户登录 |
3.3 代码示例
Express + express-session:
1 | const session = require('express-session'); |
3.4 Session 存储方案
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 内存 | 速度快 | 重启丢失、无法跨进程共享 | 开发环境 |
| Redis | 高性能、支持分布式、可设过期 | 需要额外运维 | 生产环境首选 |
| 数据库 | 持久化 | 性能较差 | 需要审计日志的场合 |
3.5 Session 的优缺点
优点:
- 用户数据存在服务端,相对安全
- 服务端可以随时主动失效(踢人下线)
- Session 中可存储任意数据,不受 4KB 限制
缺点:
- 服务端需要存储,用户量大时开销大
- 分布式系统需要共享 Session(Redis 集中存储)
- 依赖 Cookie 传递 Session ID,跨域麻烦
- 移动 App 对 Cookie 支持不友好
四、Token —— 无状态的通行证
4.1 什么是 Token?
Token 是一段由服务器签发的加密字符串,用户信息被编码在 Token 自身之中。服务器不需要存储任何东西,只要验证 Token 的签名就能确认用户身份。
最常见的实现是 JWT(JSON Web Token)。
可以理解为:你去参加一个会议,主办方发给你一个带有防伪标识的胸牌(Token),上面写了你的名字和权限。你每次进入会场,保安只要检查胸牌的真伪(验签),不需要去后台系统查你是谁。
4.2 JWT 的结构
JWT 由三部分组成,用 . 分隔:
1 | Header.Payload.Signature |
Header(头部):声明算法和类型
1 | { |
Payload(负载):存放用户数据(声明)
1 | { |
Signature(签名):防篡改
1 | HMACSHA256( |
⚠️ 重要提醒:JWT 的 Payload 只是 Base64 编码,任何人都可以解码查看内容。绝不要在 Payload 中存放密码等敏感信息!
4.3 Token 认证流程
1 | 用户登录 |
4.4 代码示例
生成与验证 JWT(Node.js):
1 | const jwt = require('jsonwebtoken'); |
4.5 Access Token 与 Refresh Token
| Access Token | Refresh Token | |
|---|---|---|
| 有效期 | 短(15分钟~2小时) | 长(7天~30天) |
| 用途 | 访问API | 换取新的 Access Token |
| 存储位置 | 内存(推荐) | HttpOnly Cookie |
| 发送频率 | 每次请求 | 仅在刷新时 |
4.6 Token 存储位置之争
| 方案 | XSS 风险 | CSRF 风险 | 持久性 | 建议 |
|---|---|---|---|---|
| localStorage | ❌ 高(JS可读) | ✅ 安全 | ✅ 持久 | 不推荐 |
| sessionStorage | ❌ 高(JS可读) | ✅ 安全 | ❌ 关闭即丢 | 不推荐 |
| HttpOnly Cookie | ✅ 安全 | ❌ 需防 CSRF | ✅ 可控 | 推荐 |
| 内存变量 | ✅ 安全 | ✅ 安全 | ❌ 刷新即丢 | Access Token 推荐 |
最佳实践:Access Token 存在内存中(刷新页面重新换取),Refresh Token 存在 HttpOnly Cookie 中。
4.7 Token 的优缺点
优点:
- 无状态:服务器不需要存储会话信息,易于水平扩展
- 跨域友好:不依赖 Cookie,任何域名都可以携带
- 移动端友好:App 原生支持 Header 传参
- 跨服务:同一 Token 可在多个微服务中验证
缺点:
- 无法主动撤销:Token 签发后在其有效期内一直有效(需要黑名单机制补救)
- 体积较大:相比 Session ID,JWT 在每次请求中占用更多带宽
- Payload 裸露:敏感信息不能放入 JWT
- 续期复杂:需要双 Token + 刷新机制
五、三者核心对比
| 维度 | Cookie | Session | Token (JWT) |
|---|---|---|---|
| 数据存储位置 | 浏览器 | 服务端 | 客户端(自包含) |
| 状态性 | 配合使用 | 有状态 | 无状态 |
| 扩展性 | — | 需要共享存储 | 天然支持分布式 |
| 移动端支持 | 差 | 差 | 优秀 |
| 安全性 | 看配置 | 服务端可控 | 看存储方式 |
| CSRF 风险 | 有 | 有 | 取决于存储位置 |
| XSS 风险 | HttpOnly 可防 | 低 | 视存储方式而定 |
| 跨域支持 | 有限 | 有限 | 原生支持 |
| 单次请求开销 | 小 | 小(仅传 ID) | 较大(完整 Token) |
| 主动失效 | 不支持 | 支持(删除 Session) | 困难(需黑名单) |
六、面试高频问题
Q1:JWT 是无状态的,"无状态"到底是什么意思?
无状态(Stateless) 指的是服务器不需要在内存或数据库中保存用户的会话数据。每个请求自包含——JWT 本身就携带用户信息,服务器只要验证签名就能确认身份。
这对水平扩展意义重大:请求打到任何一台服务器都能被正确处理,不需要共享 Session 存储。而传统 Session 模式下,如果请求被负载均衡到另一台机器,那台机器上没有对应 Session,用户就被认为"未登录"。
Q2:JWT 怎么实现"主动失效"(比如踢人下线)?
JWT 的天然缺陷是无法主动撤销。常见补救方案:
-
短期有效期 + Refresh Token 轮换:Access Token 设 15 分钟过期,过期后用 Refresh Token 换取新的。要踢人时,将 Refresh Token 加入黑名单,用户最多 15 分钟内就无法再续期。
-
Token 黑名单(Redis):将需要失效的 JWT 的
jti(JWT ID)存入 Redis,过期时间设为 JWT 的剩余有效期。中间件验证时先查黑名单。 -
版本号/时间戳法:在用户表中维护一个
tokenVersion字段。JWT 签发时嵌入版本号。踢人时递增该字段,旧版本号的 JWT 全部失效。
Q3:什么是 CSRF?为什么 SameSite Cookie 能防御它?
CSRF(跨站请求伪造):攻击者诱导用户在已登录目标网站的情况下,访问恶意页面,恶意页面会自动向目标网站发起请求。因为浏览器会自动携带目标网站的 Cookie,所以攻击者的请求会以用户身份执行。
示例:你在 bank.com 登录了,然后不小心点开了一个恶意网站。这个网站里有一个隐藏的 <img src="https://bank.com/transfer?to=hacker&amount=10000">。浏览器加载这张"图片"时,会自动携带 bank.com 的 Cookie,转账就执行了。
SameSite 属性的防御原理:
SameSite=Strict:完全禁止跨站发送 Cookie。最严格,但用户从邮件点链接进入网站时也不会带 Cookie,体验差。SameSite=Lax:只在顶级导航(如点击链接)时发送 Cookie,在<img>、<iframe>、AJAX 等子资源请求中不发送。这是大部分场景的最佳选择。SameSite=None:不做限制,但必须搭配Secure。
Q4:Token 存在 Cookie 和存在 localStorage,哪个更安全?
这是一个经典的"安全性 vs 便利性"的权衡:
localStorage 的问题:任何 JavaScript 代码都能读取 localStorage.getItem('token')。一旦网站有 XSS 漏洞(比如某个第三方脚本被污染),攻击者就能直接窃取 Token。
HttpOnly Cookie 的优势:HttpOnly 标记使 Cookie 完全对 JavaScript 不可见。即使有 XSS 漏洞,攻击者也无法直接读取 Token。但代价是引入了 CSRF 风险,需要用 SameSite + CSRF Token 做双重防护。
结论:HttpOnly + Secure + SameSite 的 Cookie 更安全。 理想的组合是 Access Token 存内存、Refresh Token 存 HttpOnly Cookie。
Q5:用户清除了浏览器 Cookie 会发生什么?
- Session 模式:服务端的 Session 还在(直到过期),但客户端丢失了 Session ID,无法再匹配。用户表现为"被登出",需要重新登录。
- JWT 模式(Token 存在 Cookie):Token 丢失,用户需要重新登录。
- JWT 模式(Token 存在 localStorage):清除 Cookie 不会清除 localStorage,Token 仍然存在,用户不受影响。
这也是很多网站"清除 Cookie"后发现登录状态还在的原因——Token 存在了 localStorage。
Q6:"记住我"功能怎么实现?
核心思路:将"登录"和"记住密码"分开处理。
- 用户勾选"记住我"登录。
- 服务端生成一个特殊的长期 Token(如
remember_token),存入数据库,同时设为 HttpOnly Cookie,maxAge设为 7~30 天。 - 用户的短期 Session 过期后,系统检测到
remember_token,自动为用户重建 Session,无需用户重新输入密码。 - 用户"退出登录"时,服务端删除数据库中对应的
remember_token,同时清除 Cookie。
1 | // 登录接口中的"记住我"逻辑 |
Q7:什么是 Session 固定攻击(Session Fixation)?如何防御?
攻击流程:
- 攻击者访问网站,获得一个 Session ID(如
sessionId=abc123)。 - 攻击者诱导用户用这个 Session ID 访问网站(如发送链接
https://site.com?sessionId=abc123)。 - 用户被诱导后用这个 Session ID 登录,服务器将用户身份和
abc123绑定。 - 攻击者自己用
sessionId=abc123访问网站,此时他以用户身份登录了。
防御方法:
- 登录后更换 Session ID:这是最重要的防御。
express-session中req.session.regenerate()可以做到。 - 不使用 URL 传递 Session ID:仅用 Cookie。
- 设置合理的 Cookie 属性:
HttpOnly+Secure+SameSite。
Q8:在 SPA 中如何优雅地处理 Token 刷新?
一个成熟的方案:
1 | // Axios 拦截器实现无感刷新 |
关键点:
- 并发请求排队:多个请求同时 401 时,只发一次刷新请求,其他排队等待。
- 重试标记:
_retry防止刷新接口本身 401 导致死循环。 - 失败兜底:刷新失败统一跳转登录页。
七、总结
这三个概念不是"三选一"的对立关系,而是不同层次的解决方案:
- Cookie 是传输工具——浏览器自动携带的存储机制
- Session 是认证模式——服务端存储会话状态
- Token 是认证模式——客户端自包含凭证
实际项目中它们经常混用:Session ID 通过 Cookie 传输,JWT 也可以用 HttpOnly Cookie 存储。理解它们各自的职责边界,比背诵"区别"更重要。
面试一句话总结
Session 是"服务器存状态,客户端存钥匙";JWT 是"客户端自己带着身份证";Cookie 是"浏览器自动帮他们传递的工具"。
面试关键词速记卡:
| 概念 | 一句话 |
|---|---|
| Cookie | 浏览器自动携带的小段数据存储,4KB 限制 |
| Session | 服务端存用户数据,客户端只存 Session ID |
| JWT | 自包含的加密令牌,无状态,天然支持分布式 |
| CSRF | 利用浏览器自动带 Cookie 的特性跨站伪造请求 |
| SameSite | Cookie 属性,控制跨站请求是否携带 Cookie |
| Refresh Token | 长期 Token,专门用来换短期 Access Token |
| HttpOnly | Cookie 属性,禁止 JS 读取,防 XSS 窃取 |