<?php
// dl.php
$id = $_GET['id'] ?? '';
$act = $_GET['act'] ?? 'display';
switch ($act) {
case 'display':
setcookie('visited', 'true', time() + 3600, '/');
// 輸出下載頁面 HTML
break;
case 'down':
if (!isset($_COOKIE['visited'])) die('盜鏈?');
// 執行下載邏輯
break;
}
缺陷:
<img src="dl.php?act=down&id=1">,瀏覽器會攜帶 Cookie)function checkReferer(array $allowedDomains): bool {
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (!$referer) return false; // 無 Referer 視策略決定
$host = parse_url($referer, PHP_URL_HOST);
return in_array($host, $allowedDomains, true);
}
// 使用
if (!checkReferer(['example.com', 'www.example.com'])) {
http_response_code(403);
exit('Forbidden: Hotlinking not allowed');
}
局限:Referer 可偽造、隱私模式/防火牆可移除、HTTPS→HTTP 不送 Referer。
原理:下載連結包含時效簽名,伺服器驗證簽名與過期時間,無需 Session/Cookie。
<?php
// 生成下載連結(頁面渲染時)
function generateDownloadUrl(string $fileId, int $ttl = 300): string {
$secret = 'your-256-bit-secret-key'; // 從配置讀取
$expire = time() + $ttl;
$payload = $fileId . '|' . $expire;
$signature = hash_hmac('sha256', $payload, $secret);
return "download.php?file=$fileId&expire=$expire&sig=$signature";
}
// 驗證下載請求
function verifyDownloadUrl(): ?string {
$fileId = $_GET['file'] ?? '';
$expire = (int)($_GET['expire'] ?? 0);
$sig = $_GET['sig'] ?? '';
if (!$fileId || !$expire || !$sig) return null;
if (time() > $expire) return null; // 過期
$secret = 'your-256-bit-secret-key';
$payload = $fileId . '|' . $expire;
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $sig)) return null; // 簽名不符
return $fileId; // 合法
}
// download.php
$fileId = verifyDownloadUrl();
if (!$fileId) { http_response_code(403); exit('連結無效或已過期'); }
// 取得檔案路徑(資料庫查詢,防 ID 遍歷)
$stmt = $pdo->prepare('SELECT path, mime, size FROM files WHERE id = ? AND deleted_at IS NULL');
$stmt->execute([$fileId]);
$file = $stmt->fetch();
if (!$file) { http_response_code(404); exit('檔案不存在'); }
// 輸出檔案(X-Sendfile/X-Accel-Redirect 最佳)
header('X-Accel-Redirect: /protected-files/' . basename($file['path']));
header('Content-Type: ' . $file['mime']);
header('Content-Length: ' . $file['size']);
header('Content-Disposition: attachment; filename="' . basename($file['path']) . '"');
exit;
完全在 Web Server 層驗證,零 PHP 開銷:
# nginx.conf
location /download/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "your-secret$uri$arg_expires";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; } # 過期
# 內部重寫至實際檔案路徑
internal;
alias /var/www/files/;
}
# PHP 生成連結
function generateNginxSecureLink(string $filePath, int $ttl = 300): string {
$secret = 'your-secret';
$expire = time() + $ttl;
$md5 = base64_encode(hash_hmac('sha1', "$filePath$expire", $secret, true));
$md5 = strtr($md5, '+/', '-_'); // URL-safe
return "/download/$filePath?md5=$md5&expires=$expire";
}
| 層級 | 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|---|
| 應用層 | 簽名 URL (HMAC) | API、私有下載、跨域分享 | 無狀態、可攜帶、精確控制 TTL、支援撤銷 | 需管理密鑰、連結較長 |
| 應用層 | Token/Session 綁定 | 會員專屬內容、需登入下載 | 可結合權限系統、支援動態決策 | 需 Session、難防熱連結、CSRF 風險 |
| Web Server | Nginx secure_link | 大流量靜態檔案、影音串流 | 極高效能、零 PHP、原生支援 | 需 Nginx、撤銷需配合 Lua/Redis |
| Web Server | Referer 檢查 | 圖片防盜鏈、簡單場景 | 配置簡單、無代碼變更 | 易繞過、誤傷隱私用戶 |
| CDN/邊緣 | 簽名 URL / Token 驗證 | 全球分發、降低源站壓力 | 最接近用戶、DDoS 緩解 | 廠商綁定、配置複雜 |
| 儲存層 | S3/OSS 預簽名 URL | 物件儲存直傳/直讀 | 無伺服器、自動 HTTPS、細粒度權限 | 依賴雲廠商、URL 長 |