3. HTTP 缓存机制:核心头与验证流程
在 OpenAPI 契约的缓存与权限验证场景中,HTTP 缓存机制是减少重复请求、降低后端负载、提升响应速度的关键技术。本节将系统讲解 Cache-Control、Expires、ETag / If-None-Match、Last-Modified / If-Modified-Since 四组核心头部的定义、优先级与协作流程。
3.1 缓存头部概览与优先级
HTTP 缓存头部按控制粒度分为“绝对过期”与“条件验证”两类。下表列出各头部的作用与优先级关系:
| 头部名称 | 方向 | 作用 | 优先级 |
|---|---|---|---|
| Cache-Control | 请求 / 响应 | 指令式控制缓存行为(max-age、no-cache、no-store 等) | 最高 |
| Expires | 响应 | 指定绝对过期时间(HTTP/1.0 遗留) | 低于 Cache-Control |
| ETag | 响应 | 资源唯一标识(哈希或版本号) | 条件验证 |
| If-None-Match | 请求 | 携带 ETag 值,询问资源是否变更 | 与 ETag 配对 |
| Last-Modified | 响应 | 资源最后修改时间 | 条件验证 |
| If-Modified-Since | 请求 | 携带时间戳,询问资源是否修改 | 与 Last-Modified 配对 |
优先级规则:Cache-Control 的 max-age 指令会覆盖 Expires 头部;ETag 的验证优先级高于 Last-Modified。当同时存在时,缓存代理优先使用 ETag 做条件请求。
3.2 Cache-Control 与 Expires:过期控制
Cache-Control 是 HTTP/1.1 定义的缓存指令头部,支持多种指令组合:
max-age=<seconds>:资源从生成时刻起可缓存的最大秒数。no-cache:每次使用前必须向源服务器验证资源是否过期。no-store:禁止任何缓存(包括代理和浏览器)。public/private:允许公共缓存或仅私有缓存。must-revalidate:过期后必须重新验证,不可直接使用。
Expires 是 HTTP/1.0 的绝对时间戳头部,格式为 Expires: Wed, 21 Oct 2025 07:28:00 GMT。当 Cache-Control 存在时,Expires 被忽略。
OpenAPI 契约缓存建议:对不频繁变更的契约文档(如 OpenAPI 规范 JSON),设置
Cache-Control: public, max-age=3600;对包含权限信息的端点,使用 no-cache 确保每次请求都验证权限。
3.3 ETag 与 If-None-Match:强验证器
ETag(Entity Tag)是服务器为资源生成的唯一标识,通常为哈希值或版本号。客户端在后续请求中通过 If-None-Match 头部携带该值,服务器比较后返回:
- 304 Not Modified:资源未变更,客户端可继续使用本地缓存。
- 200 OK:资源已变更,返回新资源及新 ETag。
# 首次响应
HTTP/1.1 200 OK
ETag: "abc123def456"
Content-Type: application/json
# 后续请求
GET /openapi/v3/spec HTTP/1.1
If-None-Match: "abc123def456"
# 未变更响应
HTTP/1.1 304 Not Modified
ETag: "abc123def456"
安全注意:ETag 不应包含敏感信息(如用户 ID、权限哈希)。若 ETag 可预测,攻击者可能通过构造 If-None-Match 探测资源是否存在。建议使用内容哈希(如 SHA-256)作为 ETag 值。
3.4 Last-Modified 与 If-Modified-Since:弱验证器
Last-Modified 记录资源的最后修改时间,精度为秒。客户端通过 If-Modified-Since 携带该时间戳,服务器判断是否返回 304 或 200。
# 首次响应
HTTP/1.1 200 OK
Last-Modified: Tue, 15 Oct 2025 12:00:00 GMT
# 后续请求
GET /openapi/v3/spec HTTP/1.1
If-Modified-Since: Tue, 15 Oct 2025 12:00:00 GMT
# 未修改响应
HTTP/1.1 304 Not Modified
Last-Modified 的局限性:
- 精度仅到秒,毫秒级变更无法区分。
- 无法检测内容是否真正变化(如元数据修改但内容未变)。
- 建议与 ETag 配合使用,ETag 优先。
3.5 缓存验证流程(SVG 图)
下图展示客户端与服务器之间的完整缓存验证流程,涵盖强缓存与条件请求两条路径:
3.6 缓存头部在 OpenAPI 契约中的典型配置
以下是一个针对 OpenAPI 规范文档的缓存响应示例,展示如何组合使用各头部:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600, must-revalidate
ETag: "d5e8c2a1b3f4"
Last-Modified: Mon, 14 Oct 2025 08:30:00 GMT
Expires: Mon, 14 Oct 2025 09:30:00 GMT
{
"openapi": "3.1.0",
"info": { "title": "用户服务 API", "version": "2.4.1" },
"paths": { ... }
}
配置要点:
public允许 CDN 或网关缓存契约文档。max-age=3600设置 1 小时强缓存有效期。must-revalidate确保过期后必须回源验证。- ETag 使用内容哈希,Last-Modified 作为辅助验证。
- Expires 作为向后兼容的备选。
3.7 权限验证场景下的缓存策略
当 OpenAPI 端点需要权限验证时,缓存策略需谨慎设计:
| 场景 | 推荐缓存策略 | 说明 |
|---|---|---|
| 公开契约文档 | public, max-age=3600 |
允许公共缓存,减少重复请求 |
| 用户专属权限数据 | private, no-cache |
仅私有缓存,每次验证权限 |
| 动态权限策略 | no-store |
禁止任何缓存,确保实时性 |
| 带 ETag 的权限资源 | private, max-age=0, must-revalidate |
结合 ETag 做条件请求,减少带宽 |
安全警告:切勿将包含权限信息的响应设置为
public 缓存。攻击者可能通过共享缓存获取其他用户的权限数据。始终使用 private 或 no-cache 保护敏感端点。