参考文献:

最近在系统性学习 Nest 的时候,发现在它当中实现 cookie、session 和 jwt 的功能区别相对比较大,因此想着系统性的记录一下。

# cookie & session

cookie 我们都比较清楚,它是保存在我们浏览器本地端的,每个 cookie 包含着一小段文本信息。当我们访问一个网站时,网站的服务器可能会在我们的浏览器上存储一个或多个 cookie。这些 cookie 包含了关于我们的信息,比如我们的偏好、身份验证令牌等。当我们再次访问该网站时,他的浏览器会将这些 cookie 发送给服务器,这样服务器就能识访问的用户并根据存储的信息提供定制化的服务。

简而言之,就是这个东西保存了用户访问这个网站的一些已知信息。

每一个 cookie 代表着一部分特定含义的信息,其中各个字段的值都是通过一定手段进行加密过的。

那么,为什么要在浏览器中引入这么一个机制呢?

从功能出发,cookie 实际上是 记录了当前用户在这个网站上的一些活动信息 。而我们知道, HTTP 是无状态的协议 ,这也就意味着 每个请求都是完全独立的 ,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次请求的发送者是不是同一个客户端。服务器与浏览器为了进行会话跟踪,就必须主动的去维护一个 状态 ,这个状态用于告诉服务端前后两个请求是否来自同一个浏览器。这个状态就需要通过 cookie 或者 session 来实现。

一般 cookie 存储的一些字段如下表所示:

字段名称 含义
name Cookie 的名称。
value Cookie 的值。
domain Cookie 所属的域。
path Cookie 的路径。仅在指定路径下的请求中,该 Cookie 会被发送到服务器。
expires Cookie 的过期时间。超过这个时间后,Cookie 将被浏览器删除。
max-age Cookie 的最大存活时间,以秒为单位。这是一个相对时间,超过这个时间后,Cookie 将被删除。
secure 如果设置了这个字段,表示 Cookie 仅在 HTTPS 连接中被发送。
HttpOnly 如果设置了这个字段,表示 Cookie 不会被 JavaScript 脚本访问。这有助于减少跨站脚本(XSS)的风险。
SameSite 用于控制 Cookie 的跨站请求策略,常见值有 StrictLaxNone

这些字段中, namevalue 是必需的,其他都是可选的,但在实际应用中会根据需要来设置,一般不会全都包括。

cookie 是从服务端发送到客户端,然后客户端将其存储到浏览器的。

原因也很简单,因为关于用户信息的数据全部在服务端,因此当然需要在鉴权完毕后通过服务端将数据发送到客户端,客户端再携带它来让服务端对这个请求进行鉴权。 这些操作基本都是利用了 HTTP 的 Header 来配置 Cookie 的传输(Set-Cookie、Cookie)。

上述流程的实现主要分为三个步骤:

当服务端需要在用户浏览器上设置一个 Cookie 时,它会在 HTTP 响应头中包含一个 Set-Cookie 字段。例如,一个 Web 服务器可能发送以下 HTTP 响应:

HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: sessionId=abc123; HttpOnly; Max-Age=3600; path=/

这个 Set-Cookie 头部包含了多个指令:

  • sessionId=abc123 :这是 Cookie 的名称和值。
  • HttpOnly :增加安全性,使得 Cookie 不能通过客户端的脚本访问。
  • Max-Age=3600 :Cookie 的存活时间,单位为秒。
  • path=/ :Cookie 适用的路径。

浏览器接收到带有 Set-Cookie 头部的 HTTP 响应后,会解析这个头部,然后按照服务器的指示将 Cookie 存储在浏览器的 Cookie 存储库中。浏览器会管理 Cookie 的存储、过期和其他属性。

一旦 Cookie 被存储在浏览器中,只要之后的请求符合 Cookie 的域和路径规则,浏览器就会自动将这些 Cookie 通过 HTTP 请求头中的 Cookie 字段发送到服务器。例如:

GET /index.html HTTP/1.1
Host: www.example.com
Cookie: sessionId=abc123

在这个请求中,浏览器将之前存储的 sessionId Cookie 发送给服务器,服务器通过这个 Cookie 识别用户或维持用户的会话状态。

# session 是什么

不过一般为了节省浏览器本地的存储空间,一般放在 cookie 里面的数据不会太多(≤4KB)。但是有一些和用户相关的数据又必须在请求的时候解析出来,那怎么办呢? 不放在浏览器存,那就放在服务器存呗。 这个时候我们的 session 就出场了。

session 是另一种跟踪用户状态的机制,它是在 服务器 上存储的。当用户访问 Web 应用时,服务器会创建一个 session 对象,为该用户会话分配一个唯一的标识符(通常称为 session ID)。这个 session ID 通常会被存储在用户的 cookie 中。用户再次发起请求时,浏览器会将包含 session ID 的 cookie 发送到服务器,服务器通过这个 ID 找到对应的 session 对象,从而了解用户的状态和数据。

也就是说,使用 session 这个机制时,需要 cookie 进行配合使用,不过 cookie 只存一个 session 的 id,发请求到服务器之后服务器就把 cookie 的 session_id 解析出来之后在自己本地通过 id 去匹配哪个 session,然后再拿信息。其实相当于 cookie 只是存一个中间信息,真正拿信息还是在服务器自己拿。

  • 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端。
  • 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等。
  • 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
  • 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些。
  • 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie。
  1. 用户第一次访问网站时,服务器创建一个 session,并生成一个唯一的 session ID。
  2. 服务器将这个 session ID 存储在用户浏览器的 cookie 中。
  3. 当用户再次发送请求时,浏览器会自动将这个包含 session ID 的 cookie 发送给服务器。
  4. 服务器接收到 cookie,提取 session ID,然后根据这个 ID 找到之前存储的 session,进而知道用户的状态。

(我个人感觉这个逻辑还是很倾向于键值对的匹配的,理解起来并不难)

# JWT

JWT 可谓是当下最流行的跨域认证解决方案了,目前的实用程度甚至超过了上述的 cookie & session。

# Token 是什么?

在了解 JWT 之前,需要先了解一下所谓的 Token。

token 本身的意思是 令牌 ,是服务端生成的一串字符串,作为客户端进行请求的一个标识。JWT 本身的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。

当然,为了防止用户篡改数据,服务器在生成这个对象的时候,会给他加密一下,就是我们看到的一个长长的字符串。

# Token 的工作流程

  1. 前端使用用户名跟密码请求首次登录;

  2. 后服务端收到请求,去验证用户名与密码是否正确;

  3. 验证成功后,服务端会根据用户 id 、用户名、定义好的秘钥、过期时间生成一个 Token ,再把这个 Token 发送给前端;

  4. 前端收到返回的 Token ,把它存储起来,比如放在 Cookie 里或者 LocalStorage 里(后者比较常用);

  5. 前端每次路由跳转,判断 LocalStroage 有无 token ,没有则跳转到登录页。有则请求获取用户信息,改变登录状态;

  6. 前端每次向服务端请求资源的时候需要在 请求头 里携带服务端签发的 Token

  7. 服务端收到请求,然后去验证前端请求里面带着的 Token 。没有或者 token 过期,返回 401 。如果验证成功,就向前端返回请求的数据。

  8. 前端得到 401 状态码,重定向到登录页面。

# JWT 是什么?

从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。下面是 FC 7519 对 JWT 做的较为正式的定义:

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)

JSON Web Token (JWT) 是一种紧凑、URL 安全的方式,用于表示要在两方之间传输的声明。 JWT 中的声明被编码为 JSON 对象,该对象用作 JSON Web 签名 (JWS) 结构的有效负载或 JSON Web 加密 (JWE) 结构的明文,从而使声明能够进行数字签名或完整性保护使用消息验证代码 (MAC) 和 / 或加密。 ——JSON 网络令牌(JWT)

Token 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

可以看出,JWT 更符合设计 RESTful API 时的「Stateless(无状态)」原则

并且, 使用 Token 认证可以有效避免 CSRF 攻击,因为 Token 一般是存在在 LocalStorage 中,使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。

# JWT 的结构

JWT 通常包含三个部分,它们之间用点( . )分隔:

  1. Header(头部)

    • 头部通常由两部分组成:令牌的类型( typ ),通常是 JWT ;以及使用的哈希算法(如 HS256 ,即 HMAC SHA-256)。
    • 例如: {"alg": "HS256", "typ": "JWT"}
  2. Payload(负载)

    • 负载包含声明(claim),声明是关于实体(通常是用户)和其他数据的语句。
    • 声明可以分为三种类型:注册的声明(如 iss (发行者)、 exp (过期时间)、 sub (主题))、公共的声明和私有的声明。
  3. Signature(签名)

    • 为了创建签名部分,你必须拿头部的编码值和负载的编码值,然后使用头部指定的哈希算法(如 HS256 )和一个密钥,对它们进行签名。
    • 签名用于验证消息在传递过程中没有被更改,并且,对于使用私钥签名的令牌,它还可以验证发行人的身份。

# JWT 的工作流程

  1. 用户登录:用户提供登录凭证(如用户名和密码)。

  2. 服务器验证:服务器验证凭证的正确性,如果验证成功,将创建一个包含用户信息和一些系统需要的数据的 JWT。

  3. 发送 JWT:服务器响应时将 JWT 发送回用户。

  4. 客户端存储 JWT:客户端将 JWT 存储在存储中,通常是浏览器的 LocalStorageSessionStorage

  5. JWT 在请求中的使用:之后客户端每次向服务器发送请求时,都会在 HTTP 头部中包含这个 JWT ,通常是在 Authorization 头部中使用 Bearer Schema 模式,就像下面这种格式:

    Authorization: Bearer xxx

    如果 token 是在授权头( Authorization header )中发送的,那么跨源资源共享(CORS)将不会成为问题,因为它不使用 cookie 。

  6. 服务器验证 JWT:服务器通过检查 JWT 的签名和有效性(如过期时间)来验证每个请求。

image-20230807204534879

# JWT 的优点

  • 自包含:JWT 包含了所有用户验证所需的信息,避免了多次查询数据库。
  • 跨域认证:非常适合单页应用(SPA),可以轻松处理跨域问题。
  • 性能好:由于 JWT 的自包含特性,减少了需要查询数据库的次数。

# JWT 的缺点

  • 安全风险:如果不正确使用或密钥泄露,JWT 可能会被恶意利用。
  • 存储限制:由于存储在客户端,如果 JWT 太大,会增加请求的负载。
  • 无状态和失效问题:JWT 一旦签发,在到期之前将持续有效,即使用户登出系统也是如此,这可能会带来一些安全隐患。

# JWT vs. Cookie & Session

# 存储位置

  • Cookie:直接存储在客户端的浏览器上。
  • Session:存储在服务器上,通常通过在客户端浏览器中存储一个 Session ID 的 Cookie 来引用服务器上的 Session 数据。
  • JWT:虽然 JWT 可以存储在任何地方,但通常存储在客户端,例如浏览器的 localStorage 或作为 Cookie 存储。

# 安全性

  • Cookie:容易受到跨站脚本攻击(XSS)和跨站请求伪造(CSRF)的攻击,需要正确设置属性如 HttpOnlySecure 来增强安全性。
  • Session:相对较安全,因为数据存储在服务器上。但如果 Session ID 被截获,会话也可以被劫持。
  • JWT:具有内置的安全机制,如数字签名。然而,如果密钥被泄露或不正确实施,其安全性也会受到威胁。

# 性能

  • Cookie JWT:由于数据存储在客户端,不需要每次请求都从服务器拉取数据,可以减轻服务器负担。
  • Session:需要服务器存储和管理 Session 数据,如果用户量大会消耗较多的服务器资源。

# 可扩展性

  • Cookie Session:不易于扩展特别是在使用多个服务器或负载均衡环境时,可能需要额外的配置如 Session 复制或中央存储。
  • JWT:由于是无状态的,易于扩展。JWT 可以在多个服务之间共享,不依赖单个服务器的会话信息。

# 过期控制

  • Cookie:可以在 Cookie 中设置 expiresmax-age 属性来控制过期时间。
  • Session:过期时间通常在服务器端控制,当用户长时间不活动或显式登出时会话结束。
  • JWT:可以在 Token 中包含过期时间( exp 声明),这提供了严格的时间控制。

# 适用场景

  • Cookie:适合存储少量的、非敏感的数据,如用户偏好设置。
  • Session:适合复杂的交互式应用,可以安全地存储敏感数据。
  • JWT:适合现代 Web 应用和移动应用,特别是在分布式微服务架构中,用于服务间的轻量级授权。