如何在 API 网关层实现 OAuth2 Client Credentials 模式鉴权?

文章导读
API 网关层实现 OAuth2 Client Credentials 模式鉴权,核心在于将认证逻辑从业务服务剥离,统一在网关层拦截并校验令牌。此方案适用于后端服务间通信(M2M)及无用户上下文的场景,能有效降低老旧系统的改造成本。
📋 目录
  1. A 架构选型与边界
  2. B 主流网关配置实战
  3. C 自研网关中间件实现
  4. D 验证与排查
  5. E 安全加固建议
  6. F 参考文档
A A

API 网关层实现 OAuth2 Client Credentials 模式鉴权,核心在于将认证逻辑从业务服务剥离,统一在网关层拦截并校验令牌。此方案适用于后端服务间通信(M2M)及无用户上下文的场景,能有效降低老旧系统的改造成本。

先说结论:Client Credentials 模式适合机器对机器通信,生产环境建议通过网关层统一对接,避免侵入原有业务代码。

  • 适合:内部服务调用、后台任务、无用户上下文的 API 访问
  • 先准备:在认证服务注册客户端获取 Client ID 和 Client Secret,Secret 需加密存储并定期轮换
  • 验收:验证令牌获取流程、令牌有效期管理及无效令牌拦截机制,确保日志可追溯

架构选型与边界

实施前需明确网关与应用的边界。基础设施网关(如 Kong、APISIX、Nginx)负责统一鉴权,业务服务仅信任网关透传的头部信息。若无法部署独立网关,可在应用层使用中间件(如 Spring Security、PHP league/oauth2-server),但需注意这属于应用层方案,不具备网关的通用性。

注意:特定语言库(如 PHP league/oauth2-server)默认仅从 Authorization Basic 头获取凭据,忽略 Body 参数,这是库的实现细节而非 OAuth2 标准。通用网关应遵循 RFC 规范,支持多种凭证传递方式,但生产环境建议强制使用 Authorization 头以提高安全性。

主流网关配置实战

以下提供两种主流开源网关的配置示例,可直接参考落地。

如何在 API 网关层实现 OAuth2 Client Credentials 模式鉴权?

方案一:Kong 网关配置

Kong 提供官方 OAuth2 插件,支持 Client Credentials 流程。需在 Kong 中启用插件并配置认证服务地址。

# kong.yml 配置片段
services:
  - name: backend-service
    url: http://upstream-service
    routes:
      - name: secure-route
        paths:
          - /api/v1
    plugins:
      - name: oauth2
        config:
          scopes:
            - read
            - write
          mandatory_scope: true
          token_expiration: 7200
          enable_client_credentials: true

配置完成后,客户端需向 Kong 指定的 token 端点请求令牌,并在后续请求 Header 中携带 Authorization: Bearer <token>

方案二:APISIX 网关配置

APISIX 通过 openid-connect 插件支持 OAuth2 校验,需配置认证服务器发现端点。

# APISIX Route 配置
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
  "uri": "/api/*",
  "plugins": {
    "openid-connect": {
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "discovery": "https://auth-server.com/.well-known/openid-configuration",
      "scope": "read write",
      "bearer_only": true
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "127.0.0.1:8080": 1
    }
  }
}'

自研网关中间件实现

若使用 Nginx + Lua 自研网关,需在 access 阶段校验 Token。以下为核心逻辑示例,包含令牌内省(Introspection)调用。

如何在 API 网关层实现 OAuth2 Client Credentials 模式鉴权?
access_by_lua_block {
    local auth_header = ngx.var.http_authorization
    if not auth_header then
        return ngx.exit(401)
    end
    local token = string.match(auth_header, "Bearer%s+(.+)")
    if not token then
        return ngx.exit(401)
    end

    -- 调用认证服务器 introspection 接口
    local res = ngx.location.capture("/introspect", {
        method = ngx.HTTP_POST,
        body = "token=" .. token
    })

    if res.status ~= 200 then
        ngx.log(ngx.ERR, "Token validation failed: ", res.status)
        return ngx.exit(401)
    end
}

对于 PHP 应用层中间件,需注意 league/oauth2-server 等库默认只从 Authorization: Basic base64(client_id:client_secret) 头里取凭据,完全忽略 POST body 中的 client_id 和 client_secret。测试时请确保 curl 命令使用 -u 参数而非 -d 传递凭证。

验证与排查

配置完成后,需通过以下步骤验证鉴权是否生效。

1. 获取令牌

curl -X POST "http://auth-server.com/oauth/token" \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
-H "Content-Type: application/x-www-form-urlencoded"

成功响应应包含 access_tokenexpires_in

如何在 API 网关层实现 OAuth2 Client Credentials 模式鉴权?

2. 访问受保护接口

curl -X GET "http://gateway.com/api/v1/resource" \
-H "Authorization: Bearer <access_token>"

3. 日志排查

若返回 401,检查网关错误日志。常见日志样例如下:

[error] 1234#0: *5678 oauth2 validation failed: invalid_token, client: 192.168.1.1, request: "GET /api/v1/resource"

如果日志里看不到任何 client 查询记录,说明请求未到达认证校验逻辑,需检查网关路由配置或插件是否生效。若使用 Kong,可通过 kong logs 查看;若使用 Nginx,检查 error.log

安全加固建议

  • Secret 管理:Client Secret 不应硬编码在代码库中,建议使用环境变量或密钥管理服务(如 Vault)存储,并建立定期轮换机制。
  • 传输安全:生产环境必须使用 HTTPS 请求获取 Token 及调用 API,防止凭证在传输层被窃听。部分网关插件(如新版 Kong)在严格模式下会强制要求 HTTPS。
  • 最小权限:Scope 不是可选配置,而是路由级权限开关。例如 GET /metrics 要求 monitor:read,网关必须在校验 token 同时比对 scope 是否覆盖当前请求路径所需权限。
  • 令牌校验:必须校验 iss(发行者)、aud(受众)、client_id 三要素,防止令牌被滥用。

参考文档