OAuth 2.0 Client Credentials 完整指南 (2026)
以正确的方式实现机器对机器(M2M)身份验证
开始使用 Hypereal 构建
通过单个 API 访问 Kling、Flux、Sora、Veo 等。免费积分开始,扩展到数百万。
无需信用卡 • 10万+ 开发者 • 企业级服务
OAuth 2.0 Client Credentials:完整指南 (2026)
OAuth 2.0 Client Credentials(客户端凭据)授权模式是实现机器对机器 (M2M) 通信的标准方式。与 Authorization Code 流程(涉及用户通过浏览器登录)不同,Client Credentials 流程专为无需人类用户参与的服务器到服务器场景而设计,例如后台任务、微服务、CLI 工具和自动化流水线。
本指南涵盖了 Client Credentials 流程的工作原理、适用场景、如何在多种语言中实现,以及应遵循的安全最佳实践。
何时使用 Client Credentials
| 场景 | 是否使用 Client Credentials? | 原因 |
|---|---|---|
| 后端服务调用另一个 API | 是 | 不需要用户上下文 |
| 从 API 获取数据的 Cron 定时任务 | 是 | 自动化,无需用户交互 |
| 微服务间的调用 | 是 | 服务器到服务器身份验证 |
| 移动应用调用您的 API | 否 | 使用 Authorization Code + PKCE |
| 网页应用代表用户操作 | 否 | 使用 Authorization Code 流程 |
| 单页面应用 (SPA) | 否 | 使用 Authorization Code + PKCE |
| IoT 设备上报数据 | 视情况而定 | 取决于设备的能力 |
规则很简单:如果请求中不涉及人类用户,Client Credentials 通常是正确的流程。
流程如何工作
Client Credentials 流程是最简单的 OAuth 2.0 授权模式。它涉及两个参与方和一个请求:
┌──────────┐ ┌────────────────────┐
│ 客户端 │ │ 授权服务器 │
│ (您的 │──── 1. 请求令牌 ──────>│ │
│ 服务器) │ (client_id + │ │
│ │ client_secret) │ │
│ │<─── 2. 访问令牌 ────────│ │
│ │ └────────────────────┘
│ │
│ │ ┌────────────────────┐
│ │──── 3. API 请求 ───────>│ 资源服务器 │
│ │ (携带访问令牌) │ (API 接口) │
│ │<─── 4. 响应 ────────────│ │
└──────────┘ └────────────────────┘
第 1 步: 您的客户端(服务器、脚本或服务)将它的 client_id 和 client_secret 发送到授权服务器的令牌端点 (token endpoint)。
第 2 步: 授权服务器验证凭据并返回访问令牌 (access token)。
第 3 步: 您的客户端使用该访问令牌调用资源服务器(您想要访问的 API)。
第 4 步: 资源服务器验证令牌并返回响应。
实现方法
第 1 步:注册您的应用程序
在使用 Client Credentials 流程之前,您需要在授权服务器(例如 Auth0、Okta、Azure AD 或自建 OAuth 服务器)上注册您的应用程序。
您将获得:
- Client ID:应用程序的公开标识符。
- Client Secret:必须保密的私钥。
第 2 步:请求访问令牌
Python
import requests
TOKEN_URL = "https://auth.example.com/oauth/token"
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
AUDIENCE = "https://api.example.com" # 您想要访问的 API
response = requests.post(
TOKEN_URL,
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"audience": AUDIENCE,
"scope": "read:data write:data",
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
token_data = response.json()
access_token = token_data["access_token"]
expires_in = token_data["expires_in"] # 通常为 3600 秒(1 小时)
print(f"Token: {access_token[:20]}...")
print(f"Expires in: {expires_in} seconds")
Node.js
const TOKEN_URL = "https://auth.example.com/oauth/token";
async function getAccessToken() {
const response = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
audience: "https://api.example.com",
scope: "read:data write:data",
}),
});
if (!response.ok) {
throw new Error(`Token request failed: ${response.status}`);
}
const data = await response.json();
return data.access_token;
}
cURL
curl -X POST https://auth.example.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-client-id" \
-d "client_secret=your-client-secret" \
-d "audience=https://api.example.com" \
-d "scope=read:data write:data"
变体:HTTP Basic Authentication
某些授权服务器要求客户端凭据通过 HTTP Basic auth 请求头发送,而不是放在请求体中:
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
TOKEN_URL,
data={
"grant_type": "client_credentials",
"scope": "read:data write:data",
},
auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
)
对应的 cURL 命令:
curl -X POST https://auth.example.com/oauth/token \
-u "your-client-id:your-client-secret" \
-d "grant_type=client_credentials" \
-d "scope=read:data write:data"
第 3 步:使用访问令牌
获得令牌后,请将其包含在 API 请求的 Authorization 标头中:
api_response = requests.get(
"https://api.example.com/v1/data",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
)
print(api_response.json())
令牌响应格式
成功的令牌响应如下所示:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:data write:data"
}
| 字段 | 描述 |
|---|---|
access_token |
用于 API 请求的 JWT 或不透明令牌 (opaque token) |
token_type |
在此流程中始终为 "Bearer" |
expires_in |
令牌有效期(秒) |
scope |
已授予的权限范围(可能与请求的范围不同) |
注意:Client Credentials 流程不返回 refresh_token。当访问令牌过期时,您只需使用相同的客户端凭据请求一个新令牌即可。
令牌缓存与管理
为每个 API 调用都请求新令牌是浪费的,并且可能会触发授权服务器的频率限制。请实现令牌缓存:
import time
import requests
class TokenManager:
def __init__(self, token_url, client_id, client_secret, audience, scope=""):
self.token_url = token_url
self.client_id = client_id
self.client_secret = client_secret
self.audience = audience
self.scope = scope
self._token = None
self._expires_at = 0
def get_token(self):
# 如果缓存的令牌仍然有效(预留 60 秒缓冲),则返回它
if self._token and time.time() < self._expires_at - 60:
return self._token
response = requests.post(
self.token_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"audience": self.audience,
"scope": self.scope,
},
)
response.raise_for_status()
data = response.json()
self._token = data["access_token"]
self._expires_at = time.time() + data["expires_in"]
return self._token
# 使用示例
token_manager = TokenManager(
token_url="https://auth.example.com/oauth/token",
client_id="your-client-id",
client_secret="your-client-secret",
audience="https://api.example.com",
)
# 始终使用 token_manager.get_token() - 它会自动处理缓存
headers = {"Authorization": f"Bearer {token_manager.get_token()}"}
特定供应商示例
Auth0
response = requests.post(
"https://your-tenant.auth0.com/oauth/token",
json={
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"audience": "https://your-api-identifier",
},
headers={"Content-Type": "application/json"},
)
注意:Auth0 使用 application/json 而不是表单编码数据。
Azure AD (Microsoft Entra ID)
tenant_id = "your-tenant-id"
response = requests.post(
f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
data={
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "https://graph.microsoft.com/.default",
},
)
Okta
response = requests.post(
"https://your-org.okta.com/oauth2/default/v1/token",
data={
"grant_type": "client_credentials",
"scope": "custom_scope",
},
auth=("your-client-id", "your-client-secret"),
)
安全最佳实践
1. 绝不泄露 Client Secrets
- 严禁将密钥提交至源代码控制。请使用环境变量或密钥管理服务。
- 严禁在客户端代码(JavaScript 包、移动端应用)中包含密钥。
- 定期轮换密钥(每 90 天是一个常见策略)。
# 正确:环境变量
export CLIENT_SECRET="your-secret-here"
# 正确:密钥管理器
aws secretsmanager get-secret-value --secret-id oauth/client-secret
2. 使用最小特权原则
只请求应用程序需要的 scopes。如果您只需要 read:data,不要请求 admin:*。
3. 在资源服务器上验证令牌
如果您正在构建接收令牌的 API,请务必进行验证:
import jwt
from jwt import PyJWKClient
JWKS_URL = "https://auth.example.com/.well-known/jwks.json"
AUDIENCE = "https://api.example.com"
ISSUER = "https://auth.example.com/"
jwks_client = PyJWKClient(JWKS_URL)
def validate_token(token: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=AUDIENCE,
issuer=ISSUER,
)
return payload
4. 使用短效令牌
配置您的授权服务器发放生命周期较短(1 小时或更短)的令牌。由于 Client Credentials 流程不使用刷新令牌,您的客户端只需在当前令牌过期时请求新令牌即可。
5. 监控与审计
记录所有的令牌请求和 API 调用。针对异常模式设置告警,例如:
- 令牌请求突然激增
- 来自异常 IP 地址的请求
- 身份验证尝试失败
常见错误与解决方案
| 错误代码 | 原因 | 解决方法 |
|---|---|---|
invalid_client |
错误的 client_id 或 client_secret | 验证凭据,检查是否存在复制粘贴错误 |
invalid_scope |
请求的 scope 未配置 | 检查您的客户端被允许使用哪些 scopes |
unauthorized_client |
客户端未获准使用此授权类型 | 在授权服务器配置中启用 "Client Credentials" 授权 |
invalid_grant |
通用的授权错误 | 检查 audience、令牌 URL 和凭据 |
access_denied |
客户端缺少权限 | 验证分配给该客户端的 API 权限 |
结论
OAuth 2.0 Client Credentials 流程实现简单,是机器对机器身份验证的标准方法。需要记住的关键点是:缓存令牌、确保密钥安全、请求最小范围的权限以及在服务器端妥善验证令牌。
如果您正在构建集成 AI 媒体生成 API(用于图像、视频、数字人或音频)的应用,并且需要与经过身份验证的服务配套使用,请关注 Hypereal AI。Hypereal 为最新的生成式 AI 模型提供统一的 API,支持简单的 API Key 身份验证和按需付费模式。
