2. HTTP无状态与认证需求

HTTP 协议从设计之初就有一个非常鲜明的特征:无状态(stateless)。这意味着服务器处理每一个请求时,默认不会记住之前发生过什么。同一个客户端连续发来两个请求,服务器在协议层面把它们视为两个互不相关的独立事件。这个特性带来了简单、可扩展、易缓存的优势,但也直接制造了一个现实问题:网站怎么知道“你是谁”?

设想一个最朴素的登录场景。用户在登录页提交用户名和密码,服务器校验通过后返回“登录成功”。紧接着用户点击“我的订单”,浏览器又发来一个全新的请求。由于 HTTP 无状态,这个新请求里没有任何信息能证明它和刚才那次登录来自同一个人。服务器只能再次要求认证,否则无法区分这是张三还是李四。显然,让用户每点一次页面就重新输入密码是不可接受的。认证需求由此产生:服务器必须有一种机制,在多次无状态请求之间识别出同一个用户,并维持其登录状态。

Cookie:把状态“寄存”在客户端

最早的解决方案是 Cookie。服务器在登录成功的响应头中写入 Set-Cookie,浏览器收到后把这段数据保存下来;之后每次向同一域名发起请求,浏览器都会自动在请求头中带上 Cookie。这样服务器就能从请求头里读到之前写入的标识,从而“认出”这个客户端。

Cookie 的本质是客户端存储 + 自动携带。它解决了“请求之间如何关联”的问题,但很快暴露出新的矛盾:如果把用户身份、权限、余额等敏感信息直接放在 Cookie 里,客户端可以随意查看甚至篡改;如果只放一个无意义的编号,服务器又需要另一块地方来保存这个编号对应的真实数据。于是 Session 登场。

Session:把状态“寄存”在服务端

Session 的思路是:服务器在内存或存储中创建一份会话记录,保存用户身份、登录时间、权限等信息,然后只把一个不可预测的 Session ID 通过 Cookie 发给浏览器。浏览器后续请求携带这个 ID,服务器拿 ID 去自己的会话存储中查找对应数据。这样敏感信息留在服务端,客户端只持有一个“钥匙”。

Session 模式在单体应用中非常成熟,但它有一个隐含前提:会话数据必须能被处理该请求的服务器访问到。当系统扩展到多台服务器、需要负载均衡时,如果 Session 只存在某一台机器的内存里,用户下一次请求被转发到另一台机器就会“掉登录”。常见的应对方式包括会话粘滞、Session 复制、集中式 Session 存储(如 Redis)。这些方案有效,但都增加了架构复杂度和运维成本。

Token:把状态“编码”进凭证本身

Token 模式换了一个思路:不再让服务器保存会话数据,而是把必要的身份信息经过签名后直接放进一个令牌里,交给客户端保存。服务器收到 Token 后,只需验证签名和有效期,就能从令牌内容中还原出用户身份,无需查询集中式会话存储。这就是常说的自包含令牌,JWT(JSON Web Token)是其中一种典型实现。

以 JWT 为例,它由三部分组成:头部(Header)、载荷(Payload)、签名(Signature),用点号连接。载荷中可以放置用户 ID、角色、过期时间等声明(claims)。服务器用密钥对前两部分签名,客户端无法在不被发现的情况下篡改内容。验证时服务器重新计算签名并比对,同时检查 exp 等时间声明。

// JWT 结构示意(示例,非真实密钥)
Header:  {"alg":"HS256","typ":"JWT"}
Payload: {"sub":"10086","role":"user","exp":1735689600}
Signature: HMACSHA256(base64Url(Header) + "." + base64Url(Payload), secret)

// 最终令牌形如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMDA4NiIsInJvbGUiOiJ1c2VyIiwiZXhwIjoxNzM1Njg5NjAwfQ.
签名片段

Token 模式让服务端可以“无状态”地完成认证,天然适合分布式和跨域场景。但它也带来新的权衡:令牌一旦签发,在过期前通常无法直接撤销;载荷内容对客户端可见,不能放敏感信息;过期时间设置过短会导致频繁重新登录,过长则增大泄露风险。因此实际系统往往配合刷新令牌、黑名单、短有效期访问令牌等机制使用。

认证与会话分离的设计思想

回顾 Cookie、Session、Token 的演进,会发现一条清晰的主线:认证(Authentication)解决“你是谁”,会话(Session)解决“你处于什么状态”,二者本可以独立设计。早期把它们揉在一起——Cookie 里存 Session ID,Session 里存用户状态——实现简单,但耦合严重。当系统需要多端登录、微服务拆分、跨域调用时,这种耦合就会成为瓶颈。

认证与会话分离的核心主张是:认证环节负责验证凭证并产出一个可信的身份断言(Token 或 Session ID),会话环节负责管理这个断言的生存周期、权限范围和撤销策略。两者通过清晰的接口交互,而不是彼此绑定在同一种存储或同一台服务器上。这样一来,认证服务可以独立扩展,会话状态可以集中治理,前端也可以根据场景选择把凭证放在 Cookie 还是 Authorization 头中。

HTTP 无状态下的认证演进:Cookie → Session → Token HTTP 无状态 请求之间互不关联 服务器默认“记不住”用户 Cookie 客户端存储 + 自动携带 解决请求关联,但可被篡改 Session 服务端保存会话数据 客户端只持 Session ID Token 自包含凭证 签名验证身份 认证与会话分离:两个独立关注点 认证域 Authentication 校验凭证:密码 / 验证码 / 证书 产出身份断言:Token 或 Session ID 签名 / 加密,保证断言可信 会话域 Session 管理生存周期:过期 / 续期 / 刷新 权限范围:角色 / 作用域 / 资源 撤销策略:黑名单 / 短有效期 身份断言 接口交互 分离后:认证服务可独立扩展,会话状态可集中治理,前端可灵活选择 Cookie 或 Authorization 头承载凭证

从工程落地角度看,认证与会话分离并不意味着一定要用 JWT。它强调的是职责边界:认证服务只回答“凭证是否正确、身份是谁”,并签发一个有时效、可验证的断言;会话服务则回答“这个断言当前是否仍然有效、能访问哪些资源、何时应该失效”。在单体应用中,这两者可能仍由同一进程承担,但代码结构上应保持清晰分层;在微服务或前后端分离架构中,它们往往被拆成独立的认证中心和会话/权限中心。

理解要点:Cookie 是传输载体,Session 是服务端状态,Token 是自包含凭证。三者不是简单的替代关系,而是不同阶段针对“无状态 HTTP 如何维持认证”给出的不同权衡。认证与会话分离,则是把这些权衡从实现细节提升为架构原则。

实际设计时,可以先问三个问题:凭证放在哪里、状态由谁保存、失效如何控制。如果凭证放在 Cookie 且状态由服务端保存,就是经典 Session 模式;如果凭证是签名 Token 且状态编码在令牌内,就是无状态 Token 模式;如果 Token 只承载身份、权限和会话状态仍由服务端集中管理,就是两者结合的混合模式。选择哪种,取决于系统的规模、跨域需求、撤销要求和运维能力,而不是追逐某一种“最新方案”。