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 头以提高安全性。
主流网关配置实战
以下提供两种主流开源网关的配置示例,可直接参考落地。
方案一: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)调用。
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_token 和 expires_in。
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 三要素,防止令牌被滥用。