本文目录导读:

- 方法一:使用 cURL(最常用,无需额外依赖)
- 方法二:使用 Guzzle(适合复杂项目,依赖 Composer)
- 方法三:PHP 原生 (file_get_contents) — 简单场景
- 注意事项与最佳实践
- 完整示例:带缓存和 Basic Auth
在 PHP 中实现 OAuth 2.0 的客户端凭证(Client Credentials)授权模式,通常用于服务器到服务器(M2M)的通信,比如服务间调用 API。
下面提供几种常见且实用的实现方法,从简单到专业:
使用 cURL(最常用,无需额外依赖)
这是最推荐的方式,因为 PHP 环境默认支持 cURL 扩展。
获取 Token
<?php
function getAccessToken($tokenUrl, $clientId, $clientSecret) {
$ch = curl_init();
// 设置请求头(Basic Auth 或 表单参数,二选一)
// 方式 A:将凭证放在请求体中
$postFields = [
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
];
// 方式 B:使用 HTTP Basic Auth(更符合规范,推荐)
// 不需要在 body 中传 client_id/secret
// $headers = [
// 'Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret),
// 'Content-Type: application/x-www-form-urlencoded',
// ];
// $postFields = ['grant_type' => 'client_credentials'];
curl_setopt_array($ch, [
CURLOPT_URL => $tokenUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($postFields),
// CURLOPT_HTTPHEADER => $headers, // 如果用方式B,取消此行注释
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, // 生产环境建议开启
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
$data = json_decode($response, true);
if (isset($data['access_token'])) {
return $data; // 通常包含 access_token, expires_in, token_type
}
}
throw new Exception('Failed to get token: ' . $response);
}
// 使用示例
try {
$tokenData = getAccessToken(
'https://api.example.com/oauth/token',
'your_client_id',
'your_client_secret'
);
$accessToken = $tokenData['access_token'];
echo "Token 获取成功: " . $accessToken;
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
使用 Token 调用 API
function callApi($apiUrl, $accessToken) {
$ch = curl_init();
$headers = [
'Authorization: Bearer ' . $accessToken,
'Accept: application/json',
];
curl_setopt_array($ch, [
CURLOPT_URL => $apiUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status_code' => $httpCode,
'body' => json_decode($response, true)
];
}
// 使用示例
$result = callApi('https://api.example.com/v1/users', $accessToken);
if ($result['status_code'] === 200) {
print_r($result['body']);
} else {
echo "API 调用失败: " . $result['status_code'];
}
使用 Guzzle(适合复杂项目,依赖 Composer)
如果你已经在使用 Guzzle HTTP 客户端,这是更优雅的方案。
composer require guzzlehttp/guzzle
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
// 1. 获取 Token
try {
$response = $client->post('https://api.example.com/oauth/token', [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => 'your_client_id',
'client_secret' => 'your_client_secret',
],
'timeout' => 10,
]);
$tokenData = json_decode($response->getBody(), true);
$accessToken = $tokenData['access_token'];
echo "Token: " . $accessToken;
// 2. 调用 API
$apiResponse = $client->get('https://api.example.com/v1/resource', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
],
'timeout' => 15,
]);
if ($apiResponse->getStatusCode() === 200) {
$data = json_decode($apiResponse->getBody(), true);
print_r($data);
}
} catch (\GuzzleHttp\Exception\RequestException $e) {
echo "HTTP 错误: " . $e->getMessage();
if ($e->hasResponse()) {
echo $e->getResponse()->getBody();
}
}
PHP 原生 (file_get_contents) — 简单场景
适用于没有 cURL 的环境(例如某些云函数),但功能有限。
<?php
$tokenUrl = 'https://api.example.com/oauth/token';
$options = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query([
'grant_type' => 'client_credentials',
'client_id' => 'your_client_id',
'client_secret' => 'your_client_secret',
]),
'timeout' => 10,
'ignore_errors' => true, // 要拿到错误响应体
],
];
$context = stream_context_create($options);
$result = file_get_contents($tokenUrl, false, $context);
if ($result !== false) {
$data = json_decode($result, true);
if (isset($data['access_token'])) {
echo "Token 获取成功";
// 继续调用 API...
} else {
echo "错误: " . $result;
}
} else {
echo "请求失败";
}
注意事项与最佳实践
-
凭证安全:
- 绝对不要将 client_id 和 client_secret 硬编码在代码中或提交到 Git。
- 建议使用环境变量(
getenv('CLIENT_ID'))或专门的配置管理服务。
-
Token 缓存:
- 客户端凭证 Token 通常有时效(如 1 小时),且不涉及用户,务必缓存 Token,避免每次都请求。
- 简单做法:将 Token 存入 Redis/Memcached,设置过期时间(比
expires_in提前 60 秒)。
function getCachedToken($cache, $tokenUrl, $clientId, $clientSecret) { $cacheKey = 'oauth_token_' . $clientId; $tokenData = $cache->get($cacheKey); if ($tokenData === false) { $tokenData = getAccessToken($tokenUrl, $clientId, $clientSecret); $cache->set($cacheKey, $tokenData, $tokenData['expires_in'] - 60); } return $tokenData['access_token']; } -
错误处理:
- 检查 HTTP 状态码。
- 检查响应的
scope字段,确保返回的权限符合预期。
-
请求头选择:
- 大多数 OAuth 服务器(如 Auth0, Okta, Azure AD)支持将凭证放在 Body 中,但推荐使用 HTTP Basic Auth(
Authorization: Basic ...),这是 OAuth 2.0 规范推荐的方式,安全性更高(避免在日志中泄露)。
- 大多数 OAuth 服务器(如 Auth0, Okta, Azure AD)支持将凭证放在 Body 中,但推荐使用 HTTP Basic Auth(
完整示例:带缓存和 Basic Auth
<?php
class OAuthClientCredentials {
private $tokenUrl;
private $clientId;
private $clientSecret;
private $cache;
public function __construct($tokenUrl, $clientId, $clientSecret, $cache = null) {
$this->tokenUrl = $tokenUrl;
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->cache = $cache; // 可以是 Redis/APCu 等
}
public function getAccessToken() {
if ($this->cache !== null) {
$cached = $this->cache->get('access_token');
if ($cached !== false) {
return $cached;
}
}
$ch = curl_init();
$headers = [
'Authorization: Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret),
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json',
];
curl_setopt_array($ch, [
CURLOPT_URL => $this->tokenUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => 'grant_type=client_credentials',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception("Token 请求失败 (HTTP $status): $response");
}
$data = json_decode($response, true);
if (!isset($data['access_token'])) {
throw new Exception("无效的 Token 响应: " . $response);
}
// 缓存 token,提前 60 秒过期
if ($this->cache !== null) {
$this->cache->set('access_token', $data['access_token'], $data['expires_in'] - 60);
}
return $data['access_token'];
}
}
这样你就有了一个可复用的客户端凭证认证类,可以安全高效地调用受保护的 API 了。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播