各管什么
Cookie 是浏览器存的一小段键值,每次请求自动带上是身份凭证的载体。Session 是服务端保存的用户状态,通常只把一个随机 session id 放进 Cookie,真正的用户数据在服务端。
登录流程
- 用户登录,服务端校验密码后创建 session,存
user_id; - 把 session id 写入 Cookie 返回;
- 之后每次请求,浏览器带上 Cookie,服务端用 id 查到用户。
JWT 不是 Session
JWT 把状态编码进令牌本身(自带签名),服务端可不查库。它适合无状态服务,但吊销难——令牌在有效期内一直有效,无法像 session 那样随手删除。
Cookie 安全标记
- HttpOnly:禁止 JS 读取,防 XSS 偷令牌;
- Secure:只走 HTTPS;
- SameSite:限制跨站携带,缓解 CSRF。
实战案例:三个登录态“莫名其妙失效”
- 跳转回来就没登录了:从第三方站点回跳时 Cookie 丢失,多为
SameSite=Lax拦下了跨站请求。若确实需要跨站携带,应显式设SameSite=None; Secure并确认走 HTTPS。 - 同名 Cookie 互相覆盖:主域与子域各写了一个同名 Cookie,浏览器会一起发送、服务端取到旧值。写 Cookie 时明确
Domain与Path,或改名区分。 - 用户操作到一半被登出:服务端把 session 存内存,扩容或多实例后另一台机器查不到会话。改用 Redis 等共享存储,或改为无状态令牌。
常见问题(FAQ)
Session id 该多长?至少 128 位随机值,用密码学安全随机数生成,别用自增 id 或时间戳。为什么登录后要换 session id?防止会话固定攻击:登录前后 id 相同,攻击者可预先埋一个已知 id 等你登录。Cookie 和 LocalStorage 存令牌哪个好?Cookie 配 HttpOnly 能挡住 XSS 读取,更适合放会话凭证;LocalStorage 会被任意脚本读取。JWT 过期了怎么续?常见做法是短效访问令牌 + 长效刷新令牌,刷新令牌可吊销并单独存储。
动手试试
生成强会话密钥:密码生成器。
会话存储的选型与扩容
- 内存存储:单机最省事,但多实例下会“时灵时不灵”,扩容后必须迁移到共享存储;
- Redis 等共享存储:最常见的方案,注意设置过期时间、序列化格式与连接池,并做好故障时的降级(如拒绝新登录而不是全部放行);
- 数据库存储:适合需要审计与长期保留的场景,但每次请求都查库会成为瓶颈,需要加缓存;
- 加密 Cookie 存储:把状态放在加密签名的 Cookie 里,服务端无状态,代价是容量受限且难以即时吊销;
- 容量估算:按“活跃用户数 × 平均会话大小”估算,并预留峰值,避免促销时共享存储被打满。
过期与续期策略
建议同时设置“空闲超时”与“绝对超时”:前者在用户长时间不操作后失效,后者限制会话总时长。续期应滑动更新空闲超时,但不应无限延长绝对超时;对高敏感操作(改密码、支付)可要求重新验证身份,而不是仅依赖会话有效。
多端与服务化下的会话处理
- 多端登录策略要先定:是否允许同一账号在多个设备同时在线、新登录是否踢掉旧会话,这类规则直接影响存储设计,应在实现前确定。
- 服务间传递要显式:内部服务调用时应显式传递调用方身份,而不是依赖共享的会话存储,避免服务被内部调用时绕过鉴权。
- 会话数据尽量精简:会话中只保存标识与必要状态,详细资料按需查询。会话过大不仅占用存储,也会让每次请求的传输与解析成本上升。
- 兼容无状态场景:网关、边缘函数等无法访问共享存储的组件,应通过令牌传递有限信息,而不是要求它们也能读写会话。
- 监控会话异常:同一会话在短时间内出现在多个地理位置、单账号会话数量骤增,都应触发告警并支持快速失效。
会话设计的关键是提前明确“谁负责保存状态、谁负责校验状态”,把这两件事分开之后,扩容与安全加固都会容易很多。