許多網站啟用 Referer 防盜鏈,導致外部網站無法直接 `` 引用圖片。常見解法是建立**伺服器端代理**:PHP 以伺服器 IP 取得圖片,再轉交給前端。本文提供完整實作、快取優化、安全防護與法律/道德提醒。
<?php
// proxy_image.php
$url = $_GET['url'] ?? '';
if (!$url) { http_response_code(400); exit('Missing url'); }
// 基本驗證:只允許 http/https、限定域名白名單(強烈建議)
$allowedHosts = ['img.example.com', 'cdn.example.org'];
$parsed = parse_url($url);
if (!$parsed || !in_array($parsed['scheme'], ['http','https']) || !in_array($parsed['host'], $allowedHosts)) {
http_response_code(403); exit('Forbidden');
}
// 副檔名白名單
$ext = strtolower(pathinfo($parsed['path'], PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg','jpeg','png','gif','webp'])) {
http_response_code(400); exit('Invalid format');
}
// 快取檔案命名:URL hash + 副檔名
$cacheDir = __DIR__ . '/cache/images/';
if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true);
$cacheFile = $cacheDir . sha1($url) . '.' . $ext;
// 快取命中直接輸出
if (is_file($cacheFile)) {
$mime = [
'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png',
'gif'=>'image/gif','webp'=>'image/webp'
][$ext];
header('Content-Type: ' . $mime);
header('Content-Length: ' . filesize($cacheFile));
header('Cache-Control: public, max-age=86400');
readfile($cacheFile);
exit;
}
// 遠程抓取(帶上偽裝 User-Agent、Referer)
$ctx = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "User-Agent: Mozilla/5.0 (compatible; ImageProxy/1.0)\r\n" .
"Referer: https://{$parsed['host']}/\r\n",
'timeout' => 15,
'follow_location' => 1,
'max_redirects' => 3,
],
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false], // 僅測試用,生產請開啟驗證
]);
$data = @file_get_contents($url, false, $ctx);
if ($data === false) {
http_response_code(502); exit('Upstream fetch failed');
}
// 儲存快取
file_put_contents($cacheFile, $data);
// 輸出
$mime = [
'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png',
'gif'=>'image/gif','webp'=>'image/webp'
][$ext];
header('Content-Type: ' . $mime);
header('Content-Length: ' . strlen($data));
header('Cache-Control: public, max-age=86400');
echo $data;
<!-- 原本直接引用會 403 -->
<!-- <img src="https://cdn.example.org/photo.jpg"> -->
<!-- 改用代理 -->
<img src="/proxy_image.php?url=https://cdn.example.org/photo.jpg">
| 優化點 | 實作方向 |
|---|---|
| 快取清理 | Cron 定期刪除超過 N 天/總大小超過限制的快取檔 |
| 記憶體安全 | 大圖片改用 fopen + fread 串流輸出,避免 file_get_contents 佔滿記憶體 |
| 並發控制 | 同一 URL 同時請求時,僅一個進程抓取,其等待快取完成(檔案鎖 flock) |
| CDN 加速 | 代理輸出加上 Cache-Control,前端掛 Nginx/Cloudflare 快取 |
| 圖片處理 | 整合 Intervention/Image 或 GD,支援即時縮圖、水印、格式轉換 |
| 監控告警 | 記錄上游狀態碼、延遲、失敗率,接入 Prometheus/Grafana |
'verify_peer' => true`,避免中間人攻擊。| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| PHP 代理 | 可控性強、可快取、可處理圖片 | 消耗伺服器頻寬/CPU、需維護 | 自有業務、合法授權場景 |
| Nginx proxy_pass | 效能極高、配置簡單 | 無法在 PHP 層做邏輯/權限 | 純轉發、高併發 |
| 前端 Referer Policy | 零後端成本 | 瀏覽器支援度不一、無法解決嚴格防盜鏈 | 輔助手段 |
| 直接授權/簽名 URL | 合法、安全、無額外成本 | 需對方配合 | 商業合作、B2B 整合 |
PHP 代理下載可解決防盜鏈顯示問題,核心在於**伺服器端取圖 + 本地快取 + 安全白名單**。但**技術手段不等於合法授權**,部署前務必確認法律風險。生產環境建議採用 Nginx `proxy_pass` + `proxy_cache` 作為基礎層,PHP 僅處理權限驗證、圖片處理等業務邏輯,達成效能與安全平衡。