Skip to content

07 · 鉴权 Security:Token / OAuth2 / API Key 全集

生活类比:鉴权就是进小区/楼/办公室的门禁系统—— apiKey = 门禁卡(身份卡,刷一下就过) http basic = 跟保安报姓名 + 密码 bearer (JWT) = 自带 GPS 的智能手环(卡里写着你身份 + 权限 + 失效时间) OAuth2 = "用微信登录" 的扫码授权 OpenID Connect = OAuth2 的升级版(除了授权还能告诉你"这人是谁")


1. 鉴权在 OpenAPI 里怎么写:两步

步骤 1:在 components.securitySchemes定义鉴权方案

yaml
components:
  securitySchemes:
    bearerAuth:                       # ← 你给方案起的名字
      type: http
      scheme: bearer
      bearerFormat: JWT

步骤 2:在全局单个接口应用

yaml
# 全局应用(所有接口默认要鉴权)
security:
  - bearerAuth: []                    # 引用上面定义的名字

paths:
  /login:
    post:
      security: []                    # 单个接口可以覆盖:登录免鉴权
      ...
  /users:
    get:                              # 没写 security,继承全局
      ...

数组里 [] 是 OAuth2 的 scope 列表,非 OAuth2 方案就空数组。


2. 五种 securitySchemes 一览

type子类型适用场景
apiKeyin: header / query / cookie内部系统、简单 SaaS
httpscheme: basic老接口、内网管理后台
httpscheme: bearer现代 REST 主流(JWT / opaque token)
oauth24 种 flow第三方登录(GitHub / 微信 / Google)
openIdConnect-OAuth2 + 标准用户信息(OIDC)
mutualTLS (3.1)-高安全的 B2B 接口

3. apiKey · 最简单的钥匙

3.1 放在 header(最常见)

yaml
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key                 # 头的名字

请求示例:

GET /users HTTP/1.1
X-API-Key: sk_live_abc123def456

3.2 放在 query(不推荐,会被 access log 记录)

yaml
ApiKeyAuth:
  type: apiKey
  in: query
  name: api_key
GET /users?api_key=sk_live_abc123
yaml
SessionAuth:
  type: apiKey
  in: cookie
  name: SESSIONID

实际工程:90% 用 header,简单清晰。


4. http basic · 古老但还活着

yaml
components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic

请求里:

GET /admin HTTP/1.1
Authorization: Basic YWxpY2U6cGFzc3dvcmQ=
                     ↑ base64('alice:password')

Base64 不是加密!抓包就能解出明文。只能配合 HTTPS 用。 实际场景:内网管理工具、Nginx 临时保护页


5. http bearer · 现代主流

yaml
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT       # 或 opaque、PASETO 等(仅描述用,不影响验证)

请求里:

GET /me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiI.eyJzdWIiOiIxMjM0NSJ9.signature

5.1 JWT 长啥样?

header.payload.signature
   |       |       |
   |       |       └── 服务端用密钥签名,防篡改
   |       └────────── base64({"sub": "user-123", "exp": 1700000000, "role": "admin"})
   └────────────────── base64({"alg": "HS256", "typ": "JWT"})

把整段贴到 https://jwt.io 能直接解出来看(payload 是明文 base64!别在里面塞密码)。

5.2 完整示例

yaml
openapi: 3.0.3
info: { title: 带鉴权的 API, version: 1.0.0 }

security:                                    # ← 全局默认要 bearer
  - bearerAuth: []

paths:
  /login:
    post:
      security: []                            # 登录免鉴权
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                username: { type: string }
                password: { type: string, format: password }
      responses:
        '200':
          description: 登录成功,返回 JWT
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }

  /me:
    get:
      summary: 获取当前用户信息
      responses:
        '200': { description: OK }
        '401': { description: 未登录或 token 过期 }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

在 Swagger UI 上:右上角会出现 🔒 Authorize 按钮,点开输入 token,之后所有 Try it out 都自动带上 Authorization: Bearer xxx


6. OAuth2 · 第三方授权

6.1 4 种 Flow(授权流程)

Flow谁用现状
authorizationCodeWeb 应用(带后端)✅ 推荐
implicit纯前端 SPA⚠️ 已废弃,改用 PKCE
password第一方应用(自己 App 自己登录)⚠️ 不推荐了
clientCredentials服务到服务(M2M)✅ 后端 API 调用

6.2 完整 OAuth2 schema(最常见的 authorizationCode)

yaml
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://example.com/oauth/authorize
          tokenUrl: https://example.com/oauth/token
          refreshUrl: https://example.com/oauth/refresh
          scopes:
            read:user: 读取用户信息
            write:user: 修改用户信息
            read:order: 读取订单
            write:order: 创建订单

6.3 在接口上用 scope

yaml
paths:
  /users/me:
    get:
      security:
        - OAuth2: [read:user]            # ← 这个接口需要 read:user 权限
  /orders:
    post:
      security:
        - OAuth2: [write:order]          # 需要 write:order

6.4 OAuth2 流程图

1. 用户点 "用 GitHub 登录"

2. 浏览器跳转到 GitHub 的 authorizationUrl
   GET https://github.com/login/oauth/authorize?client_id=xxx&scope=read:user&redirect_uri=...

3. 用户在 GitHub 上同意授权

4. GitHub 回跳到你的 redirect_uri,带上 code
   GET https://yourapp.com/callback?code=abcd

5. 你的后端用 code 去 tokenUrl 换 access_token
   POST https://github.com/login/oauth/access_token

6. 拿到 token 后,用 Authorization: Bearer xxx 调 GitHub API

7. OpenID Connect

OIDC = OAuth2 + 标准化的 userinfo 接口 + ID Token。

yaml
components:
  securitySchemes:
    OIDC:
      type: openIdConnect
      openIdConnectUrl: https://accounts.google.com/.well-known/openid-configuration

一行配置,工具自动从 well-known URL 拉取所有 endpoint 和支持的 scope。Google / Auth0 / Keycloak 都用这套


8. 多种鉴权同时存在 · 组合规则

8.1 二选一("或")

yaml
security:
  - bearerAuth: []
  - apiKeyAuth: []

→ "带 Bearer Token 或 API Key 任意一种"。

8.2 同时满足("与")

yaml
security:
  - bearerAuth: []
    apiKeyAuth: []

→ "Bearer 和 API Key 都要带"(看仔细,少一个 -)。

8.3 完全不需要鉴权

yaml
security: []

9. 实战:典型混合鉴权 yaml

yaml
openapi: 3.0.3
info: { title: 混合鉴权示例, version: 1.0.0 }

security:
  - bearerAuth: []              # 默认要 JWT

paths:
  /healthz:
    get:
      security: []              # 健康检查免鉴权
      responses: { '200': { description: pong } }

  /users:
    get:
      security:
        - bearerAuth: []
        - apiKeyAuth: []        # 二选一:用户 token 或 API key
      responses: { '200': { description: OK } }

  /admin/reset:
    post:
      security:
        - bearerAuth: []
          apiKeyAuth: []        # 双重鉴权("与")
      responses: { '200': { description: OK } }

  /external/sync:
    post:
      security:
        - OAuth2: [write:order, write:user]    # OAuth2 多 scope
      responses: { '200': { description: OK } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://oauth.example.com/authorize
          tokenUrl: https://oauth.example.com/token
          scopes:
            read:user: 读用户
            write:user: 写用户
            read:order: 读订单
            write:order: 写订单

10. Swagger UI 的 "Authorize" 按钮

写完 securitySchemes,UI 右上角自动出现 🔒 Authorize 按钮:

┌────────────────── Available authorizations ──────────────────┐
│                                                              │
│  bearerAuth (http, Bearer)                                   │
│    Value: [_________________________]   [Authorize]          │
│                                                              │
│  apiKeyAuth (apiKey, header: X-API-Key)                       │
│    Value: [_________________________]   [Authorize]          │
│                                                              │
└──────────────────────────────────────────────────────────────┘

输入一次,所有接口的 Try it out 都自动带上对应的 header。调试神器


11. 常见坑

11.1 把 token 放进了 yaml 里

千万不要!

yaml
# 巨坑!别把真 token 写进 yaml,提交 Git 会泄漏
security:
  - bearerAuth: ['eyJhbGciOiJIUzI1NiI...']

security 数组里的内容是 scope 名字,不是 token!

11.2 Authorization 头自己写在 parameters

会冲突

yaml
parameters:
  - name: Authorization        # 错!
    in: header
    schema: { type: string }

应该用 securitySchemes

11.3 bearerFormat 误以为是验证逻辑

bearerFormat 只是给文档读者看的提示("我这是 JWT"),不会被工具拿来验证 token。验证逻辑在后端。


12. 章末面试题速览

  1. JWT 的 payload 是加密的吗?不是!只是 base64 编码,谁拿到都能解。所以别把密码、隐私信息写进 payload
  2. OAuth2 的 implicit flow 为什么被废弃? → token 走 URL fragment 返回,浏览器历史 / 日志容易泄漏;现在 SPA 推荐 authorizationCode + PKCE
  3. HTTP Basic 跟 Bearer 的区别? → Basic 用"用户名 + 密码"明文 base64 每次都传;Bearer 用 token,登录一次后一直用,泄漏了至少能 revoke。

🎬 可视化演示

下方 demo 是一个 "鉴权方案选择器"——选不同方案,看到对应的 yaml 和模拟 HTTP 请求。

→ 打开 07_security/demo.html

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗