You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 15:57:19