PHP 怎么PHP 客户端凭证

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 客户端凭证

  1. 方法一:使用 cURL(最常用,无需额外依赖)
  2. 方法二:使用 Guzzle(适合复杂项目,依赖 Composer)
  3. 方法三:PHP 原生 (file_get_contents) — 简单场景
  4. 注意事项与最佳实践
  5. 完整示例:带缓存和 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 "请求失败";
}

注意事项与最佳实践

  1. 凭证安全

    • 绝对不要将 client_id 和 client_secret 硬编码在代码中或提交到 Git。
    • 建议使用环境变量(getenv('CLIENT_ID'))或专门的配置管理服务。
  2. 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'];
    }
  3. 错误处理

    • 检查 HTTP 状态码。
    • 检查响应的 scope 字段,确保返回的权限符合预期。
  4. 请求头选择

    • 大多数 OAuth 服务器(如 Auth0, Okta, Azure AD)支持将凭证放在 Body 中,但推荐使用 HTTP Basic AuthAuthorization: Basic ...),这是 OAuth 2.0 规范推荐的方式,安全性更高(避免在日志中泄露)。

完整示例:带缓存和 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 了。

抱歉,评论功能暂时关闭!