会话管理
概要
会话管理(session management),是弥补 HTTP“无状态”的性质、跨请求保持用户登录状态的机制。HTTP 本来每个请求都是独立的,不带有前后的上下文。在会话管理中,认证成功时服务器会发放一个唯一的会话 ID,保存在浏览器的 Cookie 里。之后的请求中,服务器验证 Cookie 里包含的会话 ID,把它当作已登录的用户来处理。
会话 ID 的生成与保存
会话 ID 要用密码学上安全的随机数生成器来生成。会使用 UUID v4 或 256 位的随机字节串等。可预测的会话 ID(连续编号、基于时间戳等)会成为会话劫持攻击的目标,所以要避免使用。
在服务器端,把与会话 ID 关联的用户信息保存在数据库或内存存储中。使用 DynamoDB 这样的 NoSQL 数据库时,把会话 ID 设为分区键,并设置 TTL(有效期)来自动删除过期的会话,这样的设计很高效。
Cookie 的安全属性
会话 Cookie 需要设置多个安全属性。HttpOnly 属性禁止 JavaScript 访问 Cookie,降低会话 ID 因 XSS 攻击被窃取的风险。Secure 属性限制 Cookie 只在 HTTPS 通信中发送。SameSite 属性缓解 CSRF 攻击。
这些属性哪怕忘了设置 1 个,会话劫持的攻击面就会扩大。尤其是缺少 HttpOnly 时,XSS 漏洞被利用时,会话 ID 可能会被脚本读取。最好默认把安全属性全部启用,除非有明确的理由,否则不要禁用。
与 JWT 的比较
作为会话管理的替代方案,有 JWT(JSON Web Token)。JWT 不在服务器端保存会话信息,而是把用户信息包含在令牌本身里。服务器只要验证令牌的签名就行,所以不需要查询数据库。
但 JWT 有一个结构上的弱点:无法立即让已发放的令牌失效。用户修改了密码,或检测到非法访问时,要强制让现有的令牌失效,就需要黑名单之类的机制,这部分就要在服务器端持有状态。在会话管理中,只要从数据库中删除会话 ID,就能在服务器端让该会话失效。