Cookie、Session、JWT

1. HTTP

HTTP 协议本身是无状态的:

  • 无状态意味着每个 HTTP 请求都是独立的,服务器不会默认保留之前的请求信息。
  • 例如,即使你连续发送两个请求(比如先登录,再访问个人主页),服务器不会自动知道这两个请求来自同一个用户。

虽然 HTTP 本身无状态,但实际应用(如网站登录、购物车)需要状态,你肯定不想在登录淘宝后,因为需要打开购物车而再次登录。

因此开发者使用额外技术实现状态的持久,其核心思想都是 存储

在客户端发送第一次 HTTP 请求之后,服务器就检查请求是否合法(用户名和密码是否正确),请求合法后服务器会通过 Set-Cookie 头部向客户端(浏览器)发送 Cookie,之后客户端每次请求都携带这个 Cookie,这样以后的 HTTP 请求就不用重复验证了。

以需要登陆的场景为例,服务器会将客户端的账号密码加密并返回 Cookie,浏览器保存 Cookie,之后在后续 HTTP 请求中携带该 Cookie,服务器收到 Cookie 后会对 Cookie 解密得到账号和密码信息,并在数据库中比对。如果浏览器没有收到 Cookie,就会让客户端重新登陆。

Cookie 策略中,Cookie 完全由客户端存储,服务器无状态。对于非敏感数据,比如用户语言偏好、主题设置等,Cookie 是一种非常简单且对服务器友好的策略。

但是对于一些敏感数据,例如用户的账号和密码信息,Cookie 是直接存储在客户端(浏览器)当中的,尽管服务器可能对其进行了加密处理,这依然是很不安全,只要电脑被黑,在 Cookie 中的重要信息就会被泄露。

其次,除了第一次 HTTP 请求,后续每一次请求都要携带 Cookie,但实际上多数请求并不需要验证 Cookie,这就很不灵活,还会造成大量浪费。

3. Session

将(客户端)浏览器与服务器的通信视为 会话(Session),每个会话有一个唯一的 SessionID 来标识,服务器在第一次收到客户端的请求并验证通过后,会与客户端建立 Session,生成 SessionID 保存在服务器中,然后将 SessionID 通过 Set-CookieCookie 的形式返回给客户端,并且包含这个 Cookie 的有效期等信息。

除了 Cookie,还有别的方法可以保存这个 SessionID,例如 URL 参数,隐藏域。但是 Cookie 已经被证明是这三种方式中最方便和最安全的。从安全的观点,如果不是全部也是绝大多数针对基于 Cookie 的会话管理机制的攻击对于 URL 或是隐藏域机制同样适用,但是反过来却不一定,这就让 Cookie 成为从安全考虑的最佳选择。

这个 SessionID 就起到了账号+密码的功能,之后客户端每次发送 HTTP 请求都会包含这个 SessionID 而无需包含账号和密码信息,服务器则验证这个 SessionID 是否存在且合法。

相较于传统的 CookieCookie-Session 策略中,客户端不需要直接保存账号和密码信息,而是保存服务器生成的 SessionID,这样即使黑客得到了我们的 Cookie,也不能从中得到一些敏感信息。

3.2 解决了但没完全解决

一个问题是,如果黑客窃取了我们的 SessionID,那么它是否能仿冒我们的身份与服务器通信?

答案是可以的,这就是所谓的 会话劫持(Session Hijacking)。这是使用 Cookie 无法避免的问题,HTTP 协议是无状态的,为了维持状态,我们别无选择。

攻击方式:

  • 网络嗅探:通过中间人攻击(如公共Wi-Fi)截获未加密的 HTTP 流量。
  • XSS 攻击:通过注入恶意脚本窃取文档中的 Cookie(document.cookie)。
  • 物理访问:直接获取用户设备或浏览器 Cookie 文件。
  • 预测或伪造:弱 Session-ID 生成算法可能被破解。

如何防范 Session 劫持?

  1. 使用 HTTPS
    • 加密整个通信过程,防止网络嗅探获取 Session-ID。
  2. 设置 Cookie 安全属性
    • Secure:仅通过 HTTPS 传输 Cookie。
    • HttpOnly:禁止 JavaScript 访问 Cookie,防范 XSS。
    • SameSite=Strict/Lax:阻止跨站请求伪造(CSRF)。
  3. Session 管理增强
    • 短期有效性:设置较短的 Session 过期时间,或定期更新 Session-ID。
    • 绑定用户特征:Session 与用户 IP、User-Agent 等绑定(需权衡灵活性)。
    • 服务端主动清理:用户登出时立即销毁 Session。
  4. 防御 XSS 和 CSRF
    • 对用户输入严格过滤,避免 XSS 漏洞。
    • 结合 CSRF Token 增强敏感操作验证。
  5. 监控与日志
    • 记录异常 Session 使用行为(如多地登录、频繁更换 IP)。

其他替代方案:

  • Token-Based 验证(如 JWT):
    • 无状态,但需妥善处理 Token 存储和刷新逻辑。
    • 仍需防范 XSS 和 Token 泄露风险。
  • 多因素认证(MFA)
    • 即使 Session-ID 泄露,攻击者仍需第二因素(如短信验证码)才能登录。

3.3 安全性之外呢

虽然 Cookie-Session 在安全性方便已经做好的比较好了,但这个策略在 拓展性 方面还不够理想。

这主要是因为服务器需要保存 SessionID。如果我们将服务器从一台拓展为多台以拓展服务器的访问能力(这在当前环境下这很常见了),不同服务器之间共享 SessionID 是一个很大的问题。

这是一个比较经典的分布式环境下如何处理共享数据的问题了。

由于客户端对服务器的访问往往不是固定的,由于负载均衡,访问速度和其它因素的影响,你可能经常在不同的服务器间进行访问,你肯定不希望在不同服务器间进行访问时需要重新进行登录吧,毕竟客户端是感知不到当前是在访问那台服务器的,我们只知道,有时候莫名其妙需要重新登录,因为此时我们访问的服务器发生了变化,所以客户端需要重新在当前服务器获取 SessionID

如果我们选择在不同服务器之间共享 SessionID,但这显然不是一个很好的办法:

(1)性能瓶颈

  • 内存占用高:每台服务器需保存全量 Session 数据,浪费资源。
  • 同步延迟:Session 数据的变更(如用户权限更新)需实时同步到所有服务器,网络开销大,可能引发一致性问题。
    • 例如:用户退出登录,服务器 A 删除了 Session,但服务器 B 未及时同步,导致用户仍能通过 B 访问。

(2)扩展性差

  • 新增服务器成本高:每加入一个新节点,需从其他服务器全量复制 Session 数据,启动时间随集群规模增长而增加。
  • 跨机房/地域问题:如果服务器分布在不同的地理位置,Session 同步的延迟和丢包率会显著上升。

(3)可靠性风险

  • 单点故障:若某台服务器 Session 数据损坏,可能污染整个集群。
  • 垃圾回收困难:分散的 Session 数据可能导致过期数据清理不及时(如用户退出后 Session 未被所有节点删除)。

你可能会想,那好办,将会话数据从服务器内存剥离,集中存储到高性能的共享存储中(如 Redis、Memcached),这样客户端请求依然通过服务器进行,我们也可以正常对服务器进行拓展,然后所有服务器都访问这个共享存储来管理会话数据。

不过,聪明的你可能已经发现问题了,如果所有服务器都依赖于这一个共享存储,如果该存储宕机,例如 Session 雪崩,就会导致全站不可用,一个解决方案是 Redis 哨兵/集群 + 本地降级策略。

但不管怎么解决,依然是面多加水,水多加面,没有从根本上解决问题。

4. JWT

4.1 JWT 是什么

JWT 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息(通常是身份验证或声明信息)。它以紧凑的、URL 安全的 JSON 对象形式表示,可以被签名(如使用 HMAC 或 RSA)以确保数据的完整性和真实性。

JWT 的核心理念是:将用户状态信息直接存储在客户端(如浏览器或移动端),而不是存储在服务器端(如 Session)。服务器只需验证 Token 的合法性,无需维护会话状态,从而实现无状态(Stateless)认证

因此在 JWT 策略下,服务器不需要保存会话数据,避免了 Cookie-Session 策略在分布式环境下保存会话数据上的问题。

4.2 JWT 的结构

一个 JWT 通常由三部分组成,用 . 分隔:

Header.Payload.Signature

例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

这三个部分当中,HeaderPayload 都是直接经过 Base64Url 编码得到的,这意味着它们可以被很容易的经过解码得到,但单单得到这些数据并没有太大的作用。JWT 的核心部分在于 signature,服务器根据 HeaderPayload,通过Header 中指定的加密算法生成 signature,返回给客户端。由于在加密时需要借助服务器的私钥,因此这是很难破解的。

其实也不必须是服务器的私钥,只要是只有服务器知道的数据,用这个数据来辅助加密即可,这个过程就类似于加盐。

下次接收到 Token 时只需要验证 signature 即可。验证通过后,直接信任 Payload 中的数据(如用户 ID、角色等)。

(1)Header(头部)

描述 Token类型签名算法,如:

{
  "alg": "HS256",  // 签名算法(如 HS256、RS256)
  "typ": "JWT"     // Token 类型
}

→ 经过 Base64Url 编码后形成第一部分。

(2)Payload(负载)

存放实际的数据(称为 Claims),例如用户 ID、角色、过期时间等。

{
  "sub": "1234567890",  // 用户标识(标准声明)
  "name": "John Doe",   // 自定义声明
  "iat": 1516239022     // 签发时间(Issued At)
}

→ 经过 Base64Url 编码后形成第二部分。

Payload 的三种声明类型

类型 说明
Registered Claims 预定义标准字段(如 iss 签发者、exp 过期时间、sub 主题)。
Public Claims 公开的自定义字段(需避免冲突,建议使用命名空间如 com.example.name)。
Private Claims 私有的自定义字段(用于业务数据,如 user_id)。

(3)Signature(签名)

用于验证 Token 是否被篡改,具体的就是 HeaderPayload 两部分,计算方式为:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

服务器在收到客户端发送的 Token 之后:

  • 解码 HeaderPayload(Base64Url 解码)。
  • 用预存的密钥(Secret Key)或公钥(Public Key)重新计算签名,与 JWT 中的 Signature 比对。

4.3 JWT 的工作原理

  1. 用户登录:客户端提交用户名/密码,服务器验证后生成 JWT 并返回。
  2. 客户端存储:浏览器将 JWT 存入 localStorageCookie(推荐使用 HttpOnly Cookie 防 XSS)。
  3. 后续请求:客户端在 Authorization 请求头携带 JWT(如 Bearer <token>)。
  4. 服务器验证:服务器检查签名是否有效,并解析 Payload 获取用户信息。
客户端 (Browser/App)      服务器 (API)
      |                       |
      |--- 用户名/密码  ------>|
      |                       |
      |<----- JWT ------------|
      |                       |
      |--- 请求 + JWT -------->|
      |                       |
      |<----- 数据 -----------|

其中,最核心的部分就是对 JWT 中 signature 的验证。

4.4 JWT 的问题

由于在 JWT 策略中,服务器不保存任何会话状态,因此服务器就没有能力主动让客户端下线或注销的,也即一旦服务器对某个客户端产生了 Token,这个 Token 就是在其过期之前一直可用的。

其次就是,我们前面提到过,JWT 的数据部分 HeaderPayload 在传输过程中仅仅只是通过 Base64Url 进行了编码,实际上和明文没什么区别,这意味着这部分数据在传输过程中并不是安全的,因此我们不能在 Token 中包含敏感数据。

还有就是,Token 包含了三个字段,每个字段又包含了很多信息,因此它的占用是比较大的,在网络传输的过程中比 Cookie-Session 的方式占用更多网络资源。

参考

https://www.bilibili.com/video/BV1ob4y1Y7Ep/?spm_id_from=333.788.recommend_more_video.-1&vd_source=38033fe3a1f136728a1d6f8acf710b51

https://www.bilibili.com/video/BV1NT421m7b4?spm_id_from=333.788.recommend_more_video.-1&vd_source=38033fe3a1f136728a1d6f8acf710b51

posted @ 2025-05-20 19:01  光風霽月  阅读(58)  评论(0)    收藏  举报