首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP cURL 存取 HTTPS API 完整指南(SSL 驗證、CA 憑證、錯誤處理、現代封裝)

     編輯:PHP 相關知識

核心問題:為什麼 HTTPS 請求會失敗?

cURL 預設驗證伺服器 SSL 憑證(CURLOPT_SSL_VERIFYPEER=true),若伺服器憑證:

  • 自簽、過期、網域不符、信任鏈不完整 → 驗證失敗 → curl_exec 回傳 false。
  • 許多開發者直接關閉驗證(CURLOPT_SSL_VERIFYPEER=false),**極度危險**,等同裸奔 HTTP,遭受中間人攻擊 (MITM) 風險。

正確做法:提供 CA 根憑證

// 1. 下載最新 CA 根憑證束 (cacert.pem)
// curl -o cacert.pem https://curl.se/ca/cacert.pem
// 放置於專案可讀目錄,如 /etc/ssl/certs/cacert.pem 或專案 config/ssl/cacert.pem

$caCertPath = __DIR__ . '/config/ssl/cacert.pem';

$ch = curl_init('https://api.example.com/endpoint');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query(['key' => 'value']),
    CURLOPT_SSL_VERIFYPEER => true,  // 必須開啟
    CURLOPT_SSL_VERIFYHOST => 2,     // 嚴格檢查 CN/SAN
    CURLOPT_CAINFO         => $caCertPath, // 指定 CA 束
    CURLOPT_TIMEOUT        => 10,
]);
$response = curl_exec($ch);
if ($response === false) {
    throw new RuntimeException('cURL error: ' . curl_error($ch));
}
curl_close($ch);

進階封裝:可重用的 HttpClient 類別

<?php
declare(strict_types=1);

class HttpClient {
    private $ch;
    private $caCertPath;
    private $defaultTimeout = 10;

    public function __construct(?string $caCertPath = null) {
        $this->ch = curl_init();
        $this->caCertPath = $caCertPath ?? __DIR__ . '/config/ssl/cacert.pem';
        curl_setopt_array($this->ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_CAINFO         => $this->caCertPath,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_HTTPHEADER     => ['Expect:'], // 避免 100-continue
        ]);
    }

    public function __destruct() {
        curl_close($this->ch);
    }

    public function post(string $url, array $data = [], int $timeout = 0): string {
        $opts = [
            CURLOPT_URL        => $url,
            CURLOPT_POST       => true,
            CURLOPT_POSTFIELDS => $data, // 陣列自動 multipart/form-data,字串則 application/x-www-form-urlencoded
            CURLOPT_TIMEOUT    => $timeout ?: $this->defaultTimeout,
        ];
        // JSON 請求支援
        if (isset($data['_json'])) {
            $opts[CURLOPT_POSTFIELDS] = json_encode($data['_json']);
            $opts[CURLOPT_HTTPHEADER] = ['Content-Type: application/json', 'Expect:'];
        }
        curl_setopt_array($this->ch, $opts);
        return $this->execute();
    }

    public function get(string $url, array $query = [], int $timeout = 0): string {
        $url = $query ? $url . '?' . http_build_query($query) : $url;
        curl_setopt_array($this->ch, [
            CURLOPT_URL     => $url,
            CURLOPT_HTTPGET => true,
            CURLOPT_TIMEOUT => $timeout ?: $this->defaultTimeout,
        ]);
        return $this->execute();
    }

    private function execute(): string {
        $response = curl_exec($this->ch);
        if ($response === false) {
            $errno = curl_errno($this->ch);
            $error = curl_error($this->ch);
            // 重置狀態供下次使用
            curl_setopt($this->ch, CURLOPT_POST, false);
            curl_setopt($this->ch, CURLOPT_HTTPGET, false);
            throw new RuntimeException("cURL [$errno]: $error");
        }
        $httpCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
        if ($httpCode >= 400) {
            throw new HttpException("HTTP $httpCode: $response", $httpCode);
        }
        return $response;
    }
}

// 自訂例外
class HttpException extends RuntimeException {
    public int $httpCode;
    public function __construct(string $message, int $httpCode) {
        parent::__construct($message);
        $this->httpCode = $httpCode;
    }
}

常見 SSL 錯誤代碼與排查

錯誤代碼訊息原因解決
60SSL certificate problem: unable to get local issuer certificate缺少 CA 憑證或路徑錯誤設定正確 CURLOPT_CAINFO
51SSL: certificate subject name does not match target host name憑證 CN/SAN 不符請求域名確認域名正確、或伺服器配置 SNI
35SSL connect error / handshake failureTLS 版本不支援、加密套件不相容更新 cURL/OpenSSL、指定 CURLOPT_SSLVERSION
77Problem with the SSL CA certCA 檔案格式錯誤、權限不足重新下載 cacert.pem、檢查讀取權限

特殊場景處理

  • 客戶端憑證雙向驗證 (mTLS):
    curl_setopt($ch, CURLOPT_SSLCERT, '/path/client.pem');
    curl_setopt($ch, CURLOPT_SSLKEY, '/path/client.key');
    curl_setopt($ch, CURLOPT_SSLKEYPASSWD, 'keypassword');
    
  • 開發環境臨時忽略驗證(僅限本機/測試網路):
    // 僅在開發環境、非生產程式碼
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    
    務必用環境變數控制,絕不提交到版控。
  • 憑證釘選:CURLOPT_PINNEDPUBLICKEY(需 cURL 7.39+、OpenSSL 1.0.2+),防止 CA 被攻破後簽發偽造憑證。

現代替代方案:Guzzle / Symfony HttpClient

手寫 cURL 封裝易出錯、維護成本高。建議直接使用:

// Guzzle 7+
$client = new GuzzleHttp\Client(['verify' => '/path/cacert.pem']);
$response = $client->post('https://api.example.com', ['json' => ['key' => 'value']]);
$data = json_decode($response->getBody()->getContents(), true);

// Symfony HttpClient (獨立套件可用)
$client = Symfony\Component\HttpClient\HttpClient::create(['verify_host' => true, 'verify_peer' => '/path/cacert.pem']);
$response = $client->request('POST', 'https://api.example.com', ['json' => ['key' => 'value']]);
$data = $response->toArray();

兩者皆內建:PSR-18/17 相容、中介軟體、非同步、重試、熔斷、追蹤、測試雙工。

結語

PHP cURL 存取 HTTPS **必須啟用 SSL 驗證並提供正確 CA 根憑證**。關閉驗證是自殺式寫法。現代專案請直接採用 Guzzle 或 Symfony HttpClient,將 SSL/TLS 細節交給成熟庫處理,專注業務邏輯開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved