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

PHP cURL 發送 HTTPS 請求完整指南:SSL 憑證驗證與常見錯誤解決

     編輯:PHP 相關知識

PHP cURL HTTPS 請求實戰:從憑證驗證失敗到生產級最佳實務

呼叫第三方 HTTPS API 時,常遇到 SSL certificate problem: unable to get local issuer certificate 錯誤。本文詳細解析原因、提供臨時與永久解決方案,並建立生產環境可用的 cURL 封裝函數。

錯誤原因分析

cURL 預設會驗證伺服器 SSL 憑證的有效性(簽發機構、過期時間、域名匹配)。失敗常見原因:

  1. 系統 CA 憑證庫過舊或缺失
  2. 目標站點使用自簽憑證或私有 CA
  3. 中間憑證鏈不完整
  4. PHP/cURL 版本過舊不支援新加密套件

臨時解決方案(僅開發/測試環境)

<?php
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => 'https://api.example.com/endpoint',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query(['key' => 'value']),
    
    // ⚠️ 僅開發環境使用!生產環境嚴禁
    CURLOPT_SSL_VERIFYPEER => false, // 不驗證憑證真實性
    CURLOPT_SSL_VERIFYHOST => 0,     // 不驗證域名匹配
]);
$response = curl_exec($ch);

風險警告:停用驗證會暴露於中間人攻擊(MITM),絕不可用於生產環境。

永久解決方案:配置 CA 憑證包

步驟 1:下載最新 CA 憑證包

# Linux/macOS
curl -o /etc/ssl/certs/cacert.pem https://curl.se/ca/cacert.pem

# Windows (PowerShell)
Invoke-WebRequest -Uri https://curl.se/ca/cacert.pem -OutFile C:\php\cacert.pem

步驟 2:配置 php.ini

; php.ini
[curl]
curl.cainfo = "C:\php\cacert.pem"        ; Windows
; curl.cainfo = "/etc/ssl/certs/cacert.pem" ; Linux/macOS

[openssl]
openssl.cafile = "C:\php\cacert.pem"
; openssl.cafile = "/etc/ssl/certs/cacert.pem"

步驟 3:重啟 PHP-FPM / Apache

# 系統服務
sudo systemctl restart php8.2-fpm
sudo systemctl restart nginx

# 或 Docker
docker restart php-fpm

生產級 cURL 封裝函數

<?php
/**
 * 安全的 HTTPS 請求封裝
 * 
 * @param string $url 請求 URL
 * @param array $data POST 資料
 * @param array $headers 額外 Header
 * @param int $timeout 超時秒數
 * @return array ['code' => int, 'body' => string, 'headers' => array, 'error' => string|null]
 */
function curlHttps(string $url, array $data = [], array $headers = [], int $timeout = 30): array {
    $ch = curl_init();
    
    $defaultHeaders = [
        'User-Agent: PHP/' . PHP_VERSION . ' cURL/' . curl_version()['version'],
        'Accept: application/json',
        'Content-Type: application/x-www-form-urlencoded',
    ];
    
    $options = [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => !empty($data),
        CURLOPT_POSTFIELDS => $data ? http_build_query($data) : '',
        CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers),
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_CONNECTTIMEOUT => 10,
        
        // SSL 安全設定(生產環境標準)
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        // CURLOPT_CAINFO => '/path/to/cacert.pem', // 若 php.ini 未設定可在這指定
        
        // 追蹤重導向
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        
        // 錯誤處理
        CURLOPT_FAILONERROR => false, // 我們手動處理 HTTP 狀態碼
    ];
    
    curl_setopt_array($ch, $options);
    
    $response = curl_exec($ch);
    $errno = curl_errno($ch);
    $error = $errno ? curl_error($ch) : null;
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $responseHeaders = [];
    
    // 解析回應 Header
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $headerStr = substr($response, 0, $headerSize);
    $body = substr($response, $headerSize);
    
    foreach (explode("\r\n", $headerStr) as $line) {
        if (strpos($line, ':') !== false) {
            [$k, $v] = explode(':', $line, 2);
            $responseHeaders[trim($k)] = trim($v);
        }
    }
    
    curl_close($ch);
    
    return [
        'code' => $httpCode,
        'body' => $body,
        'headers' => $responseHeaders,
        'error' => $error,
        'curl_errno' => $errno,
    ];
}

// 使用範例
$result = curlHttps('https://httpbin.org/post', [
    'api_key' => 'your-key',
    'action' => 'test'
], [
    'Authorization: Bearer your-token'
]);

if ($result['error']) {
    error_log("cURL 錯誤 ({$result['curl_errno']}): {$result['error']}");
} elseif ($result['code'] >= 400) {
    error_log("HTTP {$result['code']}: {$result['body']}");
} else {
    $data = json_decode($result['body'], true);
    // 處理成功回應
}

進階場景處理

場景解決方案
客戶端憑證認證 (mTLS) CURLOPT_SSLCERT、CURLOPT_SSLKEY、CURLOPT_KEYPASSWD
自簽憑證/私有 CA 將 CA 憑證加入系統信任庫,或指定 CURLOPT_CAINFO 指向自訂 CA 檔案
憑證釘選 CURLOPT_SSL_VERIFYPEER => true + CURLOPT_PINNEDPUBLICKEY (cURL 7.39+)
HTTP/2 支援 編譯 cURL 時啟用 --with-nghttp2,PHP 7.0.7+ 自動協商
連線復用 (Keep-Alive) 單例模式重複使用 $ch,或使用 cURL 多工介面

除錯技巧

<?php
// 開啟詳細除錯輸出
$verbose = fopen('php://temp', 'rw+');
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, $verbose);

curl_exec($ch);

rewind($verbose);
$verboseLog = stream_get_contents($verbose);
echo $verboseLog; // 顯示完整 TLS 交握過程
fclose($verbose);

最佳實務總結

  1. 永不在生產環境停用 SSL 驗證
  2. 定期更新 CA 憑證包(建議每月或隨 OS 更新)
  3. 設定合理超時:連線 10s、總計 30s,視業務調整
  4. 記錄完整請求/回應:含 Header、Body、耗時、錯誤碼
  5. 實作重試機制:指數退避、冪等性保護
  6. 考慮使用成熟 HTTP Client:Guzzle、Symfony HttpClient、HTTPlug

正確處理 HTTPS 請求是系統安全的基石,投入時間建立穩健的 HTTP 客戶端層,能避免無數線上事故。

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