PHP音乐文件安全链接实现:如何防护音频源避免非法下载?
音频源安全防护解决方案
针对你音乐应用中音频可直接通过ID链接下载的问题,这里提供几个实用的防护方案,结合你的PHP代码进行修改:
1. 身份验证拦截未授权访问
首先确保只有登录用户才能获取音频资源,避免未授权的匿名访问。
修改后的代码片段:
session_start(); // 假设用户登录后会设置$_SESSION['user_id']作为身份标识 if (!isset($_SESSION['user_id'])) { http_response_code(403); echo "未授权访问"; exit; } // 你的原有代码继续执行 $filename = $file->link; $length = $file->size; header('Access-Control-Allow-Origin: *'); // ... 后续header设置和readfile逻辑
如果使用JWT令牌验证(更适合前后端分离架构):
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; // 自行实现validateJwtToken方法,验证令牌有效性和用户权限 if (empty($token) || !validateJwtToken($token)) { http_response_code(401); echo "无效的身份凭证"; exit; }
2. 生成带签名的临时访问链接
放弃固定的hostname/music/get/5格式,改用包含过期时间和加密签名的临时URL,确保链接只能在限定时间内由授权用户使用。
生成临时链接的代码(在用户请求获取音频时生成):
$musicId = 5; $expireTime = time() + 3600; // 链接1小时后过期 $secretKey = getenv('AUDIO_SIGN_SECRET'); // 从环境变量读取密钥,避免硬编码 // 生成签名数据 $data = "{$musicId}|{$expireTime}"; $signature = hash_hmac('sha256', $data, $secretKey); // 拼接临时访问链接 $tempUrl = "https://your-host/music/get?id={$musicId}&expire={$expireTime}&signature={$signature}";
后端验证临时链接的代码:
$musicId = $_GET['id'] ?? ''; $expire = $_GET['expire'] ?? 0; $signature = $_GET['signature'] ?? ''; $secretKey = getenv('AUDIO_SIGN_SECRET'); // 检查参数完整性 if (empty($musicId) || empty($expire) || empty($signature)) { http_response_code(400); echo "无效的请求参数"; exit; } // 检查链接是否过期 if (time() > $expire) { http_response_code(410); echo "链接已过期"; exit; } // 验证签名(使用hash_equals防止时序攻击) $data = "{$musicId}|{$expire}"; $validSignature = hash_hmac('sha256', $data, $secretKey); if (!hash_equals($validSignature, $signature)) { http_response_code(403); echo "无效的签名"; exit; } // 验证通过后,继续执行文件返回逻辑 $file = getMusicFileById($musicId); // 根据ID获取文件信息 $filename = $file->link; // ... 后续header设置和readfile逻辑
3. 请求频率限制(防批量下载)
限制单个用户的音频请求频率,防止爬虫或恶意用户批量下载资源。这里以Redis为例(性能优于数据库):
session_start(); $userId = $_SESSION['user_id']; // 初始化Redis连接 $redis = new Redis(); $redis->connect('localhost', 6379); $requestKey = "music_download:{$userId}"; $requestCount = $redis->incr($requestKey); // 第一次请求时设置60秒过期时间 if ($requestCount === 1) { $redis->expire($requestKey, 60); } // 限制每分钟最多5次请求 if ($requestCount > 5) { http_response_code(429); echo "请求过于频繁,请稍后再试"; exit; } // 后续逻辑...
4. 隐藏真实文件路径
将音频文件存储在Web根目录以外的位置,避免用户直接访问源文件。比如把文件放在/var/private_audio/,而Web根目录为/var/www/html/:
// 替换原有的$link和readfile逻辑 $realFilePath = "/var/private_audio/" . $filename; if (!file_exists($realFilePath)) { http_response_code(404); echo "文件不存在"; exit; } readfile($realFilePath);
5. 防盗链检查(辅助防护)
通过检查请求的Referer头,只允许自己的应用域名发起请求,防止其他网站非法嵌入你的音频链接:
$allowedDomains = ['your-app-domain.com', 'm.your-app-domain.com']; $referer = $_SERVER['HTTP_REFERER'] ?? ''; if (!empty($referer)) { $refererHost = parse_url($referer, PHP_URL_HOST); $isAllowed = false; foreach ($allowedDomains as $domain) { if (str_contains($refererHost, $domain)) { $isAllowed = true; break; } } if (!$isAllowed) { http_response_code(403); echo "禁止外部链接访问"; exit; } }
你当前的PHP实现代码:
$filename = $file->link; $length = $file->size; header('Access-Control-Allow-Origin: *'); header('Access-Control-Expose-Headers: *'); header('Content-Type: audio/mpeg'); header('Content-Length: ' . $length); header('Content-Range: bytes 0-' . ($length - 1) . '/' . $length); header( 'Content-type: audio/mpeg' ); header( 'Content-Disposition: inline;filename="'.$filename.'"' ); header( 'Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' ); header( 'Pragma: no-cache' ); header( 'Content-Transfer-Encoding: binary'); header("Accept-Ranges: 0-".$length); $link = str_replace(' ', '%20', $filename); readfile($link);
内容的提问来源于stack exchange,提问作者Asadbek Fayzullaev
相关产品推荐
相关产品推荐

