3. OpenAPI 规范回顾:securitySchemes 与 security 的定义方式

在构建 API 鉴权拦截器之前,必须准确理解 OpenAPI 3.0/3.1 规范中关于安全描述的两个核心组件:securitySchemes(安全方案定义)与 security(安全要求声明)。它们是拦截器解析鉴权规则的元数据基础。

3.1 核心概念与职责划分

securitySchemes 与 security 在规范中承担不同职责,下表梳理了二者的定位差异:

组件 位置 职责 作用域
securitySchemes 根级 components.securitySchemes 定义可用的鉴权方案类型、参数、流程 全局复用
security 根级 或 操作级(path+method) 声明哪些安全方案适用于当前 API 可覆盖、可组合
关键理解:securitySchemes 是"字典",security 是"使用声明"。拦截器先读取字典理解鉴权方式,再根据声明决定哪些端点需要校验。

3.2 securitySchemes:安全方案定义

securitySchemes 位于 components.securitySchemes 下,每个方案通过唯一名称(如 BearerAuth、ApiKeyAuth)标识。OpenAPI 3.0/3.1 支持以下四种鉴权类型:

  • http:基于 HTTP 标准鉴权头,如 Bearer、Basic、Digest。
  • apiKey:通过 Header、Query 或 Cookie 传递的静态密钥。
  • oauth2:OAuth 2.0 流程,需定义 flows(authorizationCode、clientCredentials 等)。
  • openIdConnect:基于 OpenID Connect Discovery 的鉴权。

以下是一个典型的 securitySchemes 定义示例:

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    OAuth2Auth:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/authorize
          tokenUrl: https://auth.example.com/token
          scopes:
            read: 读取资源
            write: 写入资源
避坑提示:OpenAPI 3.1 中 bearerFormat 仅为描述性字段,拦截器不应依赖它做格式校验。JWT 的完整性验证应基于签名而非格式声明。

3.3 security:安全要求声明

security 字段声明 API 需要满足的安全条件。它可以在两个层级出现:

  • 根级 security:作用于整个 API,作为默认安全要求。
  • 操作级 security:作用于特定路径+方法,覆盖根级声明。

security 的值是一个数组,每个元素是一个对象,键为 securitySchemes 中定义的名称,值为该方案所需的 scope 列表(仅 OAuth2/OpenID Connect 需要)。

# 根级:全局要求 Bearer 令牌
security:
  - BearerAuth: []

# 操作级:覆盖为 API Key + OAuth2 二选一
paths:
  /admin/users:
    get:
      security:
        - ApiKeyAuth: []
        - OAuth2Auth: [admin:read]
组合逻辑:security 数组中的多个对象是"或"关系(任一满足即可);单个对象内的多个 scope 是"与"关系(全部必须满足)。

3.4 安全方案与安全声明的映射关系

为了直观展示 securitySchemes 与 security 如何协作,下图描述了从规范定义到拦截器执行的映射流程:

securitySchemes 与 security 映射关系 components.securitySchemes BearerAuth (http/bearer) ApiKeyAuth (apiKey/header) OAuth2Auth (oauth2) security 声明 根级: BearerAuth: [] 操作级: ApiKeyAuth: [] 操作级: OAuth2Auth: [admin] 引用 拦截器解析流程:读取 securitySchemes 获取鉴权类型 → 读取 security 获取适用范围 → 按"或/与"逻辑执行校验

3.5 OpenAPI 3.0 与 3.1 的关键差异

虽然 securitySchemes 和 security 的核心结构在 3.0 和 3.1 中保持一致,但以下差异值得注意:

维度 OpenAPI 3.0 OpenAPI 3.1
规范基础 独立 JSON Schema 草案 完全基于 JSON Schema 2020-12
securitySchemes 类型 支持 http/apiKey/oauth2/openIdConnect 同上,无新增类型
bearerFormat 语义 描述性字段 描述性字段,无强制校验
oauth2 flows 定义 固定字段(implicit/password/...) 同上,但 implicit 已标记为废弃
security 空数组含义 表示无安全要求 同上
工程建议:对于新项目,优先使用 OpenAPI 3.1。如果维护存量 3.0 规范,securitySchemes 和 security 的解析逻辑可完全复用,只需注意 OAuth2 implicit flow 的废弃警告。

3.6 常见定义模式与拦截器适配

在实际工程中,security 的定义通常遵循以下几种模式,拦截器需要针对每种模式做适配:

  • 单一鉴权模式:全局只有一个 security 条目,如 security: - BearerAuth: []。拦截器只需检查 Bearer 令牌。
  • 多选一模式:security 数组包含多个对象,如 security: - ApiKeyAuth: [] - BearerAuth: []。拦截器需依次尝试,任一通过即放行。
  • 作用域限定模式:OAuth2 场景下指定 scope,如 security: - OAuth2Auth: [read, write]。拦截器需同时验证令牌和 scope 权限。
  • 无鉴权端点:操作级 security 为空数组 security: [],覆盖根级要求。拦截器应跳过该端点的鉴权检查。

以下代码片段展示了一个拦截器如何根据 security 声明动态选择校验策略:

// 伪代码:根据 security 声明选择鉴权策略
for (SecurityRequirement req : operation.getSecurity()) {
    for (String schemeName : req.keySet()) {
        SecurityScheme scheme = components.getSecuritySchemes().get(schemeName);
        switch (scheme.getType()) {
            case "http":
                if ("bearer".equals(scheme.getScheme())) {
                    return validateBearerToken(request);
                }
                break;
            case "apiKey":
                return validateApiKey(request, scheme.getIn(), scheme.getName());
            case "oauth2":
                return validateOAuth2(request, req.get(schemeName));
        }
    }
}
边界情况:当 security 数组为空(security: [])时,表示该端点显式声明无安全要求。拦截器必须正确处理此情况,不要错误地继承根级 security。

3.7 本章小结

securitySchemes 与 security 构成了 OpenAPI 安全描述的基石。securitySchemes 定义鉴权方案的"能力",security 声明 API 端点的"需求"。拦截器的核心工作就是建立从 security 声明到 securitySchemes 定义的解析链路,并据此执行相应的鉴权逻辑。理解这两者的结构、组合规则和版本差异,是后续实现拦截器测试验证与工程落地的必要前提。