Cookie、Session 与 Token
Cookie 是什么
Cookie 是浏览器按域名和路径管理的小型键值数据。服务端通过 Set-Cookie 写入,符合条件时浏览器在后续请求中自动携带 Cookie 请求头。
常见属性:
Expires/Max-Age:有效期。Domain/Path:发送范围。Secure:仅通过 HTTPS 发送。HttpOnly:禁止 JavaScript 读取,降低 Token 被 XSS 窃取的风险。SameSite:限制跨站请求携带 Cookie。
SameSite=None 必须同时设置 Secure。
Session
传统 Session 流程:
- 用户登录成功,服务端创建 Session。
- 浏览器保存只包含 Session ID 的 Cookie。
- 后续请求自动携带 Session ID。
- 服务端查询 Session 得到用户状态。
优点是服务端可以主动吊销;缺点是集群环境需要共享 Session、粘性会话或集中存储。
Session 与 Token 的优缺点对比
一句话区别:
- Session:凭证本身通常只是 Session ID,用户状态主要存在服务端。
- Token:凭证本身可以携带身份和权限信息,客户端每次请求主动带上,服务端验证 Token 判断身份。
Session 的优点:
- 服务端掌控会话状态,退出登录、封禁账号、踢设备都比较直接。
- 客户端只保存无业务含义的 Session ID,敏感信息不暴露给客户端。
- 对传统浏览器应用友好,配合
HttpOnly、Secure、SameSiteCookie 可以减少凭证被脚本读取的风险。
Session 的缺点:
- 服务端需要保存会话状态,请求量大时会增加存储和查询压力。
- 多实例部署时要解决 Session 共享问题,例如 Redis、数据库、粘性会话。
- Cookie 自动携带,请求有副作用时要额外做好 CSRF 防护。
Token 的优点:
- 可以天然适配前后端分离、移动端和第三方 API,客户端显式放到请求头即可。
- JWT 这类自包含 Token 可以减少服务端会话查询,业务服务只要拿到公钥就能验证签名。
- 适合统一认证中心签发,多业务系统或微服务共同验证。
Token 的缺点:
- Bearer Token 一旦泄露,在有效期内通常可以被直接冒用。
- 纯无状态 Token 不容易立即失效,注销、封禁、权限回收需要额外设计。
- JWT payload 默认不加密,不能放敏感信息。
- Token 过长会增加请求体积;把大量权限信息写进 Token 也容易造成权限陈旧。
面试里可以这样总结:Session 更偏“服务端有状态会话”,控制力强,但分布式扩展要处理共享状态;Token 更偏“客户端携带凭证”,跨端和跨服务更灵活,但泄露、吊销、刷新和权限实时性要设计好。实际项目不是二选一,常见方案是 HttpOnly Cookie + 服务端会话,或者 Access Token + Refresh Token + 服务端吊销状态。
Token 与 JWT
Token 是服务端签发给客户端的一段凭证。客户端后续请求携带它,服务端据此识别“请求者是谁、是否登录、能访问哪些资源”。和传统 Session ID 相比,Token 常见于前后端分离、移动端、开放 API、微服务调用等场景。
最常见的携带方式是放在请求头:
Bearer 的意思是“持有者凭证”:谁持有这段 Token,谁就可以代表对应身份发起请求。因此 Token 一旦泄露,攻击者通常不需要知道密码,也能在有效期内冒充用户。
Token 本身可以有不同形态:
JWT 是一种 Token 格式,不等于完整认证方案。一个 JWT 通常由三段组成:
header:声明算法和类型,例如alg、typ。payload:声明信息,例如用户 ID、过期时间、签发者、受众、权限范围。signature:对前两段做签名,防止内容被篡改。
JWT 的 payload 默认只是 Base64URL 编码,不是加密,不能存放密码、身份证号、银行卡号等敏感信息。签名用于校验完整性和签发者,并不会隐藏内容。任何拿到 JWT 的人都可以解码看到 payload。
JWT 常见声明:
服务端验证 Token 时不能只看“能不能解码”,至少要检查:
- 签名是否合法,算法是否符合预期。
- 是否过期,
nbf、iat等时间声明是否合理。 iss、aud是否匹配当前系统。- 用户是否仍然存在,账号是否被禁用。
- 权限范围是否满足当前接口要求。
- 如果支持吊销,还要检查黑名单、版本号或会话状态。
一个常见误区是“用了 JWT 就不需要查数据库”。JWT 可以减少每次请求查询 Session 的成本,但并不意味着永远不查服务端状态。用户禁用、密码修改、权限变更、主动退出、多设备管理、风控封禁等场景,都可能需要服务端状态参与校验。
签名算法也要注意边界:
- 对称签名,例如
HS256,签发和验证使用同一个密钥,适合单体服务或少量可信服务。 - 非对称签名,例如
RS256、ES256,认证服务用私钥签发,业务服务用公钥验证,更适合多服务验证。
业务代码不应该盲目信任 Token 里声明的算法,应该在服务端固定允许的算法列表。否则历史上出现过把 alg=none 或算法混淆当成合法输入的安全问题。
Cookie 和 localStorage 存 Token 的权衡
HttpOnly Cookie
- JavaScript 无法读取,降低凭证被 XSS 直接窃取的风险。
- 浏览器自动携带,需要正确设置
SameSite并考虑 CSRF。
localStorage
- 不会自动随请求发送,CSRF 风险模型不同。
- 任意成功执行的同源 XSS 都能读取并外传 Token。
不存在脱离场景的绝对答案。高价值 Web 会话通常优先考虑 Secure + HttpOnly + SameSite Cookie,并同时做好 XSS、CSRF 和服务端鉴权。
Access Token 与 Refresh Token
- Access Token:有效期短,用于业务接口。
- Refresh Token:有效期更长,只用于换取新的 Access Token,权限和暴露面应更小。
典型流程:
- 用户登录成功,认证服务签发 Access Token 和 Refresh Token。
- 客户端访问业务接口时携带 Access Token。
- Access Token 过期后,客户端使用 Refresh Token 调用刷新接口。
- 刷新成功后,服务端返回新的 Access Token,必要时也轮换 Refresh Token。
- 刷新失败时,客户端清理本地身份并跳转登录。
Access Token 应该短有效期、少权限、只面向业务接口。这样即使泄露,攻击窗口也相对有限。Refresh Token 有效期更长,价值更高,最好只发送到专门的刷新接口,不用于普通业务请求。
Refresh Token 常见设计:
多个请求同时发现 Token 过期时,应使用“单飞锁”:只允许一个刷新请求执行,其他请求等待同一个 Promise,避免刷新风暴。
刷新失败后要清理本地身份并跳转登录,不能无限重试。
Token 吊销有几种常见做法:
所以“JWT 无法注销”并不准确。更准确的说法是:纯无状态 JWT 无法在过期前被服务端主动失效;如果引入黑名单、版本号或会话表,就可以注销,但也会重新引入服务端状态。
前端还需要处理几个细节:
- 不要把长期有效的 Token 放进 URL,URL 容易进入浏览器历史、日志、Referer。
- 不要只依赖前端路由守卫判断登录,所有敏感接口都必须在服务端鉴权。
- 刷新接口应限制频率,并区分“过期”“无效”“被吊销”等错误。
- 权限变更后,服务端不能只相信旧 Token 里的角色声明;高风险接口应实时校验权限。
- 退出登录时,客户端清理本地凭证,服务端也应尽量吊销 Refresh Token 或当前会话。
高频追问
- 删除 Cookie 为什么必须匹配 Domain 和 Path?
HttpOnly能否防止 XSS 发起恶意请求?- JWT 如何注销?
- JWT payload 能不能放用户隐私信息?
- Session 和 Token 各自适合什么场景?
- Token 相比 Session 一定更安全吗?
- Access Token 为什么要短有效期?
- Refresh Token 为什么建议轮换?
- 跨子域登录如何设计?
- 为什么前端路由守卫不能代替服务端鉴权?

