为什么将用户标识符存储在 HTTP 请求标头字段中被认为是无状态的,而将其存储在 URI 中却不是?

问题描述

永远不应将用户标识符存储在 URI 中。根据 Roy Fielding 在他的博士论文架构风格和基于网络的软件架构设计中的§ 6.2.5 ‘REST Mismatches in URI’,这是为了强制实施 REST 无状态约束(无服务器端应用程序状态):

虽然 URI 设计符合 REST 的架构概念 标识符,仅语法不足以强制命名权限 根据资源模型定义自己的URI。 一种形式 滥用是包括识别当前用户的信息 在超媒体响应引用的所有 URI 中 此类嵌入的用户 ID 可用于维护会话 服务器上的状态,通过记录用户的行为来跟踪用户行为,或 在多个操作中携带用户偏好(例如,Hyper-G 的 网关 [84])。然而,通过违反 REST 的约束,这些 系统也会导致共享缓存变得无效,减少服务器 可扩展性,并在用户共享时导致不良影响 与他人的那些引用。

根据 Roy Fielding 的说法,HTTP/1.1 基本身份验证遵守 REST 无状态约束,他的 RFC 7235 § 5.1.2 ‘Considerations for New Authentication Schemes’

HTTP 身份验证被认为是无状态的:所有的 必须提供验证请求所需的信息 请求,而不是依赖于服务器记住之前 基于或绑定到底层的身份验证 连接超出了本规范的范围,并且本质上是 除非采取措施确保无法连接 由经过身份验证的用户以外的任何一方使用(请参阅第 2.3 节 [RFC7230])。

然而,根据 Roy Fielding 在其 RFC 7235 的 § 2.1 ‘Challenge and Response’ 中的说法,HTTP/1.1 允许在请求标头字段 Authorization 中存储用户标识符,以对用户代理进行身份验证:

希望通过源服务器验证自己的用户代理 -- 通常,但不一定,在收到 401(未经授权)后 -- 可以通过在请求中包含一个 Authorization 标头字段来实现。

那么为什么将用户标识符存储在 HTTP 请求标头字段中被认为是无状态的,而将其存储在 URI 中却不是?

在我看来,无论通道(URI、标头字段、正文)如何传输用户标识符,都可以通过源服务器识别用户代理,因此允许为该用户代理存储服务器端应用程序状态。换句话说,对我来说,身份验证实际上是实现状态通信的手段。

解决方法

查看 Fielding 对 5.1.3 (Stateless) 的描述可能会有所帮助:

从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上存储的任何上下文。因此,会话状态完全保留在客户端上。

这是“无状态”的关键思想 - 您应该能够独立于所有其他请求理解每个请求的语义。


那么为什么将用户标识符存储在 HTTP 请求标头字段中被认为是无状态的,而将其存储在 URI 中却不是?

不是。这两种机制都是无状态的(正如 Fielding 在 REST 上下文中定义的那样)。

Fielding 在 6.2.5 中描述的违反不是违反 5.1.3,而是更好地理解为违反 5.1.4 (Cache) 和 5.1。 5 (Uniform Interface)。

换句话说:在服务器上存储会话状态是不好的(它违反了 5.1.3,因此会影响您的一些扩展);无关紧要的是,您是使用 Cookie 标头中的信息、身份验证标头中的信息还是 URI 中的信息访问缓存的会话状态。

但是将会话状态键编码到您的资源标识符中与您的缓存以及您使用资源标识符作为资源表示的代理的能力有关。