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

PHP 防盜鏈完整指南:Apache/Nginx 配置、PHP 動態授權與最佳實踐

     編輯:PHP 綜合知識

防盜鏈核心原理

圖片盜鏈指其他網站直接引用你的圖片 URL (<img src="your.com/img.jpg">),消耗你的頻寬與流量成本。防禦核心在於識別請求來源 (Referer) 或 驗證訪問權限,拒絕非授權存取。

方案一:Web Server 層面阻擋 (效能最佳)

Apache (.htaccess)

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。佔位圖勿置於受保護目錄,避免無限循環。

Nginx 配置

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 允許當前域名。

方案二:PHP 動態授權輸出 (彈性最高)

適用於:需綁定用戶登入狀態、會員等級、臨時授權連結、圖片儲存於非 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]);

效能優化:X-Sendfile / X-Accel-Redirect

避免 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;
}

方案三:Nginx 密碼保護 (簡單私有相簿)

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 邊緣驗證

常見誤區與避坑

  • Referer 可偽造/缺失:瀏覽器隱私模式、防火牆、Referrer-Policy 可能移除 Referer。不可作為唯一安全依據,需配合其他驗證。
  • 直接輸出 $_GET['path']:嚴重路徑遍歷漏洞,必須用 realpath() 限制在儲存根目錄下。
  • 忽略 Content-Type:若輸出頭部錯誤,瀏覽器可能當作 HTML/JS 執行,導致 XSS。
  • 大檔案 readfile 記憶體暴增:超過 10MB 建議啟用 X-Sendfile/X-Accel-Redirect 或分塊輸出。

結語

防盜鏈沒有銀彈,需根據業務敏感度、流量規模、架構複雜度分層防禦:Web Server 層擋 90% 盜鏈流量 → PHP 層做精細權限控制 → 儲存層隔離執行權限。採用 X-Accel-Redirect 兼顧安全與效能,是現代 PHP 應用輸出受保護媒體檔案的標準做法。

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