Client Credentials Flow(客户端凭证流程)
Client Credentials Grant(RFC 6749 §4.4)用于机器对机器(M2M)认证。没有用户 — 服务以自己的身份(client_id 与 client_secret)进行认证。
何时使用此流程
- 微服务、守护进程、CI/CD 流水线 调用受保护的 API
- 没有用户 — 纯服务身份
- 服务可以 安全保管
client_secret(服务器端 secrets manager、环境变量 — 绝对不要 放在浏览器、移动 App,或发放给终端用户的 CLI)
接入之前
请管理员创建一个 confidential 客户端,具备:
- 启用 Client Credentials Flow
- 为此服务注册所需 scope(依部署而定的自定义 API scope)
- 不需要 redirect URI
您会拿到:
client_id— 可以写进 log
client_secret— 存进 secrets manager;一旦泄露立即轮换
受限的 scope:
openid与offline_access在此流程不合法,会以invalid_scope被拒。scope 是对应到一个合成的服务身份,不是用户。
运作方式
步骤 1:请求 access token
以 HTTP Basic 认证(推荐)或 form body 两种方式。端点只接受 application/x-www-form-urlencoded 正文。
HTTP Basic(依 RFC 6749 §2.3.1 推荐使用):
curl -X POST https://your-signet/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"
表单内容:
curl -X POST https://your-signet/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET"
省略 scope 会拿到此客户端已注册的全部 scope。只有在您想要 子集 时才带 scope=...。
带 Resource Indicator(RFC 8707):
curl -X POST https://your-signet/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "resource=https://api.example.com"
resource 为选填、可重复(最多 10 个),必须是绝对 http(s) URI、无 fragment、≤ 1024 字符。带入后,签发 JWT 的 aud 会绑定到这些 resource — resource server 端对自己的标识符验证 aud(见 JWT 验证 §Audience Binding)。每个值都必须在您客户端的 allowed-resources 白名单内,该白名单 默认全部拒绝:白名单为空的客户端,送任何 resource= 都会得到 400 invalid_target — 请管理员把此服务要访问的 resource 标识符加进白名单。不带 resource 时 aud 回退到部署级别的 JWT_AUDIENCE。格式不正确 或 不在白名单内的值会返回 400 invalid_target — 见 错误处理。
多 RS 部署:若同一服务需要调用多个身份不同的 resource server,请对每个 resource 各自请求一张 token(各自缓存)。一张 token 共用于多个 RS 会让 audience binding 失效 — 任何能接受此 token 的 RS 都会变成中继点。
上面用
$CLIENT_SECRET环境变量展示没问题;但在生产环境 不要把字面 secret 直接丢到命令行,argv 会出现在ps与 shell 历史里。改用curl --netrc、配置文件(-K)或语言端的 SDK。
响应:
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "<此客户端被授予的 scope>"
}
此 grant 不会签发 refresh token(RFC 6749 §4.4.3)。access token 过期时重新请求。
/oauth/revoke与/oauth/tokeninfo仍可用 — 见 Token 与撤销。
若索取的 scope 超过此客户端允许范围,Signet 会返回:
{
"error": "invalid_scope",
"error_description": "Requested scope exceeds client permissions or contains restricted scopes (openid, offline_access are not permitted)"
}
完整错误清单见 错误处理。
步骤 2:使用 token
curl -H "Authorization: Bearer ACCESS_TOKEN" https://api.example.com/resource
Resource server 应 以 Signet 的 JWKS 在本地验证 JWT — 见 JWT 验证。
辨识 M2M token:Client Credentials 发出的 token 其 JWT sub(与 user_id)字段为 client:<client_id>。resource server 可以依此区分服务调用与用户委派调用。aud claim 是您请求的 resource indicator(或 JWT_AUDIENCE 回退值)— 与用户 token 一样,要对 RS 自己的标识符验证。
步骤 3:缓存与续约
不要每次调用都换一个新 token。缓存在内存中,接近过期再换(减去安全缓冲):
// Go 伪代码
if time.Now().Add(30 * time.Second).After(expiresAt) {
accessToken, expiresAt = requestNewToken()
}
# Python
if time.time() + 30 >= expires_at:
access_token, expires_at = request_new_token()
避免多副本同时续约的雪崩:在 30 秒缓冲上加入少量随机抖动,或使用共享缓存(Redis)搭配 single-flight 续约。
安全检查清单
| 要求 | 说明 |
|---|---|
| 安全保管 secret | Secrets manager 或 runtime 注入环境变量 — 绝对不要 commit 到版本控制 |
| 全程 HTTPS | 每次 token 请求都会把 client_secret 送上链路 |
| 一服务一客户端 | 单独撤销与细粒度的服务 scope 控制 |
| 只要最小 scope | 最小权限原则 |
| 泄露即轮换 | 请管理员重新生成 secret;更新 secrets manager |
| 退避重试 | /oauth/token 有速率限制 — 见 Token 与撤销;处理 429 与 Retry-After |
| 缓存 token,不要重抓 | 尊重 expires_in;只在接近过期时续约 |
| 监控 audit log | 请管理员对异常的 CLIENT_CREDENTIALS_TOKEN_ISSUED 事件设警报 |