3. OpenAPI 中的缓存声明:定义缓存策略的字段与方法
在 OpenAPI 3.0 规范中,没有为缓存策略提供专用的顶层字段或独立的“缓存声明”对象。缓存行为是通过组合使用 Cache-Control 响应头字段、ETag、Last-Modified 等标准 HTTP 头,以及 OpenAPI 的 response 对象中的 headers 字段来声明的。这种设计遵循了“规范即契约”的原则,将缓存策略的声明权交给 HTTP 语义,而非引入专有结构。
3.1 核心声明字段:response.headers
在 OpenAPI 3.0 的 Response Object 中,headers 字段用于定义响应头。缓存策略的声明主要集中在此处。以下表格列出了最常用的缓存相关响应头及其在 OpenAPI 中的声明方式。
| HTTP 响应头 | OpenAPI 声明位置 | 说明 | 示例值 |
|---|---|---|---|
Cache-Control |
response.headers.Cache-Control |
定义缓存指令(max-age、public、private、no-cache 等) | public, max-age=3600 |
ETag |
response.headers.ETag |
实体标签,用于条件请求(强验证器) | "33a64df551425fcc55e4d42a148795d9f25f89d4" |
Last-Modified |
response.headers.Last-Modified |
资源最后修改时间(弱验证器) | Wed, 21 Oct 2023 07:28:00 GMT |
Expires |
response.headers.Expires |
指定响应过期的绝对时间(HTTP/1.0 兼容) | Thu, 01 Dec 2023 16:00:00 GMT |
Vary |
response.headers.Vary |
告知缓存代理根据哪些请求头区分缓存版本 | Accept-Encoding, Authorization |
Cache-Control 是否被显式声明。缺失该字段意味着缓存行为完全由客户端或中间件自行决定,可能导致缓存污染或数据不一致。
3.2 声明方法:在 OpenAPI 规范中编写缓存头
在 OpenAPI 3.0 中声明缓存策略,本质上是为每个响应对象定义其 headers 映射。以下是一个完整的 YAML 示例,展示了如何为 GET /users/{userId} 端点声明缓存策略。
paths:
/users/{userId}:
get:
operationId: getUserById
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
'200':
description: 成功返回用户信息
headers:
Cache-Control:
description: 缓存控制指令
schema:
type: string
example: private, max-age=300
ETag:
description: 实体标签,用于条件请求
schema:
type: string
example: "abc123"
Last-Modified:
description: 资源最后修改时间
schema:
type: string
format: date-time
example: "2023-10-21T07:28:00Z"
Vary:
description: 缓存键差异化字段
schema:
type: string
example: Authorization
content:
application/json:
schema:
$ref: '#/components/schemas/User'
Cache-Control: no-cache, no-store, must-revalidate。对于公开的静态资源(如产品列表),应声明 Cache-Control: public, max-age=86400 并配合 ETag 使用。
3.3 缓存策略的两种声明模式
根据资源特性,缓存策略的声明可分为两种模式:基于时间的缓存 和 基于验证器的缓存。下表对比了这两种模式在 OpenAPI 中的声明差异。
| 模式 | 核心字段 | 适用场景 | 审计关注点 |
|---|---|---|---|
| 基于时间 | Cache-Control: max-age、Expires |
静态资源、不频繁变化的数据 | max-age 值是否合理;是否同时声明了 Expires 作为降级 |
| 基于验证器 | ETag、Last-Modified |
动态资源、需要条件请求的场景 | ETag 是否强验证器;Last-Modified 精度是否足够 |
3.4 缓存声明与条件请求的契约关系
在 OpenAPI 规范中,声明了 ETag 或 Last-Modified 响应头,意味着客户端可以发送 If-None-Match 或 If-Modified-Since 请求头来发起条件请求。这种契约关系必须在 API 文档中明确体现。
以下 SVG 图展示了缓存声明与条件请求之间的完整契约流程。
3.5 缓存声明中的常见审计缺陷
在审计 OpenAPI 缓存声明时,以下缺陷最为常见,需要重点检查。
- 缺失 Cache-Control 字段:超过 60% 的 OpenAPI 规范未声明
Cache-Control,导致缓存行为不可控。 - ETag 与 Last-Modified 不一致:同时声明两者时,
ETag的更新频率应与Last-Modified匹配,否则客户端条件请求可能失效。 - Vary 字段声明不完整:当响应内容因
Accept-Encoding或Authorization而异时,未声明Vary会导致缓存代理返回错误的缓存版本。 - max-age 值过大或过小:未根据资源实际变化频率设置合理的
max-age,导致数据过期或缓存命中率低。
Cache-Control 必须设置为 no-store,禁止任何形式的缓存。审计时若发现此类响应缺少 no-store 指令,应标记为高风险缺陷。
3.6 缓存声明与 API 版本的关系
在 OpenAPI 3.0 中,缓存声明应随 API 版本变化而调整。以下 SVG 图展示了不同版本下缓存策略的演进关系。
3.7 审计基线:缓存声明检查清单
在契约审计中,针对 OpenAPI 缓存声明,应逐项检查以下基线条目。
- 必选检查:每个
200响应是否都声明了Cache-Control头? - 验证器检查:对于动态资源,是否同时声明了
ETag或Last-Modified? - 安全检查:包含敏感数据的响应是否设置了
Cache-Control: no-store? - 一致性检查:
ETag的 schema 类型是否为string,且示例值格式正确? - Vary 检查:当响应内容因请求头而异时,是否声明了
Vary字段? - 过期时间检查:
max-age值是否与资源实际变化频率匹配?
Cache-Control 字段的存在性和值范围。例如,规则 no-store-for-sensitive-endpoints 可确保所有包含 password 或 token 的响应路径都设置了 no-store。