圖片盜鏈指其他網站直接引用你的圖片 URL (<img src="your.com/img.jpg">),消耗你的頻寬與流量成本。防禦核心在於識別請求來源 (Referer) 或 驗證訪問權限,拒絕非授權存取。
RewriteEngine On
# 允許空 Referer (直接輸入網址/書籤)
RewriteCond %{HTTP_REFERER} !^$ [NC]
# 允許自有域名
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?yourdomain\.com [NC]
# 允許搜尋引擎爬蟲 (SEO 必須)
RewriteCond %{HTTP_REFERER} !^https?://(www\.)?(google|bing|baidu|yahoo)\. [NC]
# 符合上述條件 (非授權來源) → 替換圖片或返回 403
RewriteRule \.(jpe?g|png|gif|webp|bmp|svg)$ /images/hotlink-placeholder.png [R=302,NC,L]
# 或直接拒絕:RewriteRule \.(jpe?g|png|gif)$ - [F,NC,L]
關鍵說明:NC 大小寫不敏感;L 結束重寫;R=302 導向佔位圖;F 返回 403 Forbidden。佔位圖勿置於受保護目錄,避免無限循環。
location ~* \.(jpe?g|png|gif|webp|bmp|svg)$ {
valid_referers none blocked server_names
*.yourdomain.com
*.google.com *.bing.com *.baidu.com;
if ($invalid_referer) {
return 403; # 或 rewrite /images/hotlink.png redirect;
# return 302 /images/hotlink.png;
}
# 可選:啟用 expires、access_log off 等優化
expires 30d;
add_header Cache-Control "public";
}
valid_referers 內建指令簡潔高效,none 允許無 Referer,blocked 允許防火牆過濾後的 Referer,server_names 允許當前域名。
適用於:需綁定用戶登入狀態、會員等級、臨時授權連結、圖片儲存於非 Web 目錄等場景。
<?php
declare(strict_types=1);
class SecureImageServer
{
private const STORAGE_ROOT = __DIR__ . '/../storage/images/'; // 非 Web 目錄
private const ALLOWED_EXT = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
public function serve(string $imageId, array $authContext = []): void
{
// 1. 權限驗證 (示例:需登入且擁有權限)
if (!$this->authorize($authContext)) {
http_response_code(403);
exit('Forbidden: Insufficient permission');
}
// 2. 解析實際路徑 (資料庫查詢、雲端儲存 Key 等)
$relPath = $this->resolvePath($imageId);
if (!$relPath) {
http_response_code(404);
exit('Not Found');
}
$fullPath = self::STORAGE_ROOT . $relPath;
if (!file_exists($fullPath) || !is_readable($fullPath)) {
http_response_code(404);
exit('File Missing');
}
// 3. 安全性檢查:防止路徑遍歷
$realStorage = realpath(self::STORAGE_ROOT);
$realFile = realpath($fullPath);
if ($realFile === false || strpos($realFile, $realStorage) !== 0) {
http_response_code(400);
exit('Invalid Path');
}
// 4. MIME 偵測與輸出
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($realFile);
if (!str_starts_with($mime, 'image/')) {
http_response_code(400);
exit('Not An Image');
}
// 5. 快取頭部 (可依授權策略調整)
header('Content-Type: ' . $mime);
header('Content-Length: ' . filesize($realFile));
header('Cache-Control: private, max-age=3600, must-revalidate'); // 私有快取
header('X-Content-Type-Options: nosniff');
// 6. 輸出檔案 (大檔案建議用 readfile 或 X-Sendfile/X-Accel-Redirect)
readfile($realFile);
}
private function authorize(array $ctx): bool
{
// 實作你的邏輯:Session、JWT、簽名 URL 驗證、IP 白名單等
return !empty($ctx['user_id']); // 示例
}
private function resolvePath(string $id): ?string
{
// 從 DB 查詢:SELECT path FROM images WHERE id=? AND deleted_at IS NULL
// 示例回傳相對路徑
return 'user_123/photo_abc.jpg';
}
}
// 入口腳本 image.php
require_once 'bootstrap.php';
$server = new SecureImageServer();
$server->serve($_GET['id'] ?? '', ['user_id' => $_SESSION['uid'] ?? null]);
避免 PHP 進程讀取大檔案佔用記憶體,交還給 Web Server 輸出:
// Apache (需 mod_xsendfile)
header('X-Sendfile: ' . $realFile);
header('Content-Type: ' . $mime);
header('Content-Disposition: inline'); // 或 attachment
exit;
// Nginx (需內部 location)
header('X-Accel-Redirect: /protected-images/' . $relPath);
header('Content-Type: ' . $mime);
exit;
Nginx 對應配置:
location /protected-images/ {
internal; # 僅允許內部重導向
alias /var/www/app/storage/images/;
add_header X-Content-Type-Options nosniff;
}
location /private-album/ {
auth_basic "Private Album";
auth_basic_user_file /etc/nginx/.htpasswd; # htpasswd -c 生成
alias /var/www/app/storage/private/;
}
| 場景 | 推薦方案 | 關鍵點 |
|---|---|---|
| 公開部落格/內容站 | Nginx/Apache Referer 檢查 | 效能最好、零 PHP 開銷、允許搜尋引擎 |
| 會員制相簿/付費內容 | PHP 動態授權 + X-Accel-Redirect | 綁定業務邏輯、支援臨時連結、大檔案高效輸出 |
| 內部系統/管理後台 | Nginx Basic Auth 或 PHP Session | 簡單部署、集中權限管理 |
| API 提供圖片給合作夥伴 | 簽名 URL (Signed URL) + 期限 | 無狀態、可撤銷、支援 CDN 邊緣驗證 |
realpath() 限制在儲存根目錄下。防盜鏈沒有銀彈,需根據業務敏感度、流量規模、架構複雜度分層防禦:Web Server 層擋 90% 盜鏈流量 → PHP 層做精細權限控制 → 儲存層隔離執行權限。採用 X-Accel-Redirect 兼顧安全與效能,是現代 PHP 應用輸出受保護媒體檔案的標準做法。