Session Management

 28th April 2021 at 5:56pm

这里的谈及的 session 主要针对 web server session。C/S 结构的程序(桌面端、移动端)也存在 session 机制,这在后面我接触到后再补充。

Wikipedia 上的定义:

In computer science and networking in particular, a session is a temporary and interactive information interchange between two or more communicating devices, or between a computer and user (see login session). A session is established at a certain point in time, and then ‘torn down’ - brought to an end - at some later point.

常见的使用场景

  • Login session:用户在登录网站时,在服务端生成一个 session 并将 session ID 给到客户端,于是用户之后的操作会带上 session ID,即表示已经在登录中的状态
  • Anonymous session:比如你在未登录状态下打开京东,将商品加入购物车,你刷新页面后会发现购物车中仍然有你加入的商品。这即是服务端为这个匿名会话存储了购物车物品

实现上,一般是服务端以 cookie 形式下发一个 session ID(aka. session token)给客户端,客户端在后续请求中会带上这个 ID,于是服务端即知道哪些请求是同一个用户发出的。这里的用户,即可惜是登录状态下的用户(此时服务端的 session 数据会带有用户在网站上的身份信息),也可以是未登录的用户(匿名用户)。

Session 本质上是把用户的部分应该在客户端上持久化的信息放在了服务端。比如:

  • 出于安全性考虑的登录态信息
  • 出于方便性考虑的购物车物品

但是你并非一定要用服务端的 session。有一些场景下,把信息放在浏览器的 local storage 或者 session storage 也是可行的。比如 QQ 音乐的 web 播放器,会把播放列表中的曲目信息存在 local storage 中。

基础概念

  • Session ID: 也称 session token,是服务端下发给客户端的一个 ID,客户端在后续请求中会带上这个 ID 标识自己
  • Session 数据:服务端上存储的关于某个 session ID 的数据,比如它对应的用户名

系统及功能设计

Session 系统及编程框架的设计,需要考虑下面这些点。

支持多种 engine 来存储 session 数据

比如数据库、缓存系统等。使用缓存系统时需要考虑 session 时效性。

支持多种 serializer 来序列化/反序列化 session 数据

比如 Django 默认用 JSON 来做序列化,但是 JSON 中没有 datetime 类型,同时 JSON 要求 value 必须是 unicode,不适合存储二进制数据。这种情况下可能需要定制 serializer。

支持定制 session 的有效时间

比如是一次性(关闭浏览器即失效)还是持续生效的。持续生效的可以持续多久。

Session 续期(renewal)/失效(invalidation)

存在一些需要延长 session 有效期,或者让它立即失效的场景。

续期:

  • 有些网站在用户有访问时会自动延长 session 有效期,方便用户使用
    • 对于这种场景,建议多开一个 session ID 表示一个固定的失效时间(比如用户登录 30 天后)。服务端应试同时查看这两个 ID 来判断登录态是否过期。这样可以避免某个用户的 session ID 被黑客获取后,黑客无限期地使用它的身份
  • 大多数网站出于方便,允许一个用户存在多个有效的 session;但是少部分网站出于安全性或者收费的考虑,仅允许存在有限的有效 session。比如网银只允许用户当前有一个登录,并且 10 分钟未操作可能会话就过期了。像收费观看内容的网站,根据收费策略可能限制了 session 数,使得你只能在有限台设备看它的内容,比如端传媒
  • Django 在 session 数据有变化时会自动延长有效期(仍不太理解这个设计)

失效:

  • 用户主动登出时,服务端应该将 session 失效
  • 用户修改密码时,该用户当前所有 session 都应该失效
  • OWASP 提到当用户的权限等级变化时(比如升级、从普通用户变为管理员等),应该重新生成 session ID。还不太理解它的用意

提供给用户查看当前未失效 session 的能力

比如 Google 提供了一个查看帐号已登录设备的 页面。它显示了设备类型(手机/PC)、操作系统、设备名和最近一次登录的地区。用户如果发现帐号被盗,可以将某一设备强行登出。

测试浏览器是否支持 cookie

有用户出于保护隐私关闭了浏览器 cookie 功能,可以提供一种机制来测试用户端是否支持 cookie,比如 Django 的 test cookies

定时清理过期 session

对于主动登出的用户,服务器可以清除其 session;但是对于过期却没有登出的用户,需要定期从 session 存储中清除避免存储过度膨胀。

安全性考虑

可接受的 Session ID 交换机制

在客户端和服务端间交换 session ID 时,仅可以用 cookie 技术。有些老的框架用 URL 参数,这是非常不安全的,可能在 web server 的 access 日志等被泄露。

客户端及服务端的请求间应该开启 TLS。Session ID 的 cookie 应开启 secure flag 使其仅能在 HTTPS 形式下传输。并且应置 HTTP Only 防止被 JS 脚本获取。

SameSite 建议开成 Lax。Domain 及 Path 属性,视你的需求而设。

应当校验 session ID

客户端传输过来的 Session ID 是可以伪造的。应当像表单输入的参数一样做校验,防止被注入或者 XSS。

Session 存储

存 session 数据时应该同时加入一个加了盐及使用了不公开密码的 hash 值,防止存储被爆破 session 内容被随意修改。数据表中不应该将用户 ID 以明文存储在 session 存储中,这会导致黑客获得网站全部 session 数据后很容易发现它要攻击的用户的数据是哪一条。

防止浏览器缓存需要登录态才可查看的内容

使用 Cache-ControlPragma 来指导浏览器不去缓存需要登录态才可查看的内容。避免 Session 过期后,一些敏感信息仍然能从浏览器缓存中被读取。

攻击检测和防御

攻击者做暴力破解时,可能需要用一批 IP 发大量请求。监控这类请求以及时发现。设置限流或者验证码来防止大量尝试。

防止用户 Session ID 被盗的另一个方法是,将 Session ID 与客户端的某些属性绑定在一起,比如客户端的地理区域、设备类型等。如果发现该 Session ID 相关的客户端属性发生变化,那即有可能是被盗取。

Session Hijacking and Session Fixation

Session Hijacking 是黑客通过手段获得用户 session key,使其可以伪装成用户。这一般通过 HTTP Only 的 cookie 等手段可以解决。

Session Fixation 是指黑客通过某种手段(CSRF、中间人攻击等)将其 session key 注入到用户的浏览器中,再诱导用户在目标网站登陆。一些 session 校验没做好的网站,在用户登陆后并不会更换 session key,因此用户跟黑客共享了一个 session key,从而实现攻击。

Django Session 实现

See Django: Session.

Resources

See Also