PHP防盗链怎么实现

wen PHP项目 4

本文目录导读:

PHP防盗链怎么实现

  1. 基础防盗链实现
  2. 增强版防盗链
  3. Token 验证防盗链
  4. Apache/Nginx 层防盗链
  5. 响应式错误信息
  6. 完整示例:综合防盗链类
  7. 最佳实践建议
  8. 补充说明

我来详细讲解PHP防盗链的实现方法,包括多种场景和最佳实践。

基础防盗链实现

HTTP Referer 检查

<?php
// 防盗链配置
$allowed_hosts = [
    'www.yourdomain.com',
    'yourdomain.com',
    'localhost',
    '127.0.0.1'
];
// 获取来源信息
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$host = parse_url($referer, PHP_URL_HOST);
// 检查是否来自允许的域名
if (!empty($referer) && !in_array($host, $allowed_hosts)) {
    // 防盗链响应
    header('HTTP/1.1 403 Forbidden');
    header('Content-Type: image/gif'); // 返回空白图片
    readfile('blank.gif');
    exit;
}
// 正常输出内容
header('Content-Type: image/jpeg');
readfile('your-image.jpg');
?>

增强版防盗链

<?php
class AntiHotlink {
    private $allowed_domains = [];
    private $allow_empty_referer = false;
    private $cache_time = 3600;
    private $excluded_paths = ['/public/', '/css/', '/js/'];
    public function __construct($allowed_domains = []) {
        $this->allowed_domains = $allowed_domains;
    }
    public function check() {
        $current_url = $_SERVER['REQUEST_URI'];
        // 排除特定路径
        foreach ($this->excluded_paths as $path) {
            if (strpos($current_url, $path) !== false) {
                return true;
            }
        }
        // 获取Referer
        $referer = $_SERVER['HTTP_REFERER'] ?? '';
        // 允许空的Referer
        if ($this->allow_empty_referer && empty($referer)) {
            return true;
        }
        // 没有Referer的直接拦截
        if (empty($referer)) {
            $this->deny();
            return false;
        }
        // 解析Referer域名
        $referer_host = parse_url($referer, PHP_URL_HOST);
        $referer_host = strtolower($referer_host);
        // 检查是否在允许列表中
        foreach ($this->allowed_domains as $domain) {
            $domain = strtolower($domain);
            if ($referer_host === $domain || preg_match('/\.' . preg_quote($domain, '/') . '$/', $referer_host)) {
                return true;
            }
        }
        $this->deny();
        return false;
    }
    private function deny() {
        header('HTTP/1.1 403 Forbidden');
        // 根据请求类型返回不同响应
        $file_ext = strtolower(pathinfo($_SERVER['REQUEST_URI'], PATHINFO_EXTENSION));
        switch ($file_ext) {
            case 'jpg':
            case 'jpeg':
                header('Content-Type: image/jpeg');
                $this->outputBase64Image($this->getErrorImage('jpg'));
                break;
            case 'png':
                header('Content-Type: image/png');
                $this->outputBase64Image($this->getErrorImage('png'));
                break;
            case 'gif':
                header('Content-Type: image/gif');
                $this->outputBase64Image($this->getErrorImage('gif'));
                break;
            case 'mp3':
            case 'mp4':
                header('Content-Type: application/octet-stream');
                echo "Access Denied";
                break;
            default:
                echo "Access Denied";
        }
        exit;
    }
    private function outputBase64Image($base64_data) {
        echo base64_decode($base64_data);
    }
    private function getErrorImage($type) {
        // 返回一个1x1像素的空白图片base64编码
        return $this->getBlankImageBase64($type);
    }
    private function getBlankImageBase64($type) {
        // 1x1像素透明图片
        $images = [
            'png' => 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
            'jpg' => '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0SwD/9k=',
            'gif' => 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
        ];
        return $images[$type] ?? $images['png'];
    }
}
// 使用示例
$anti_hotlink = new AntiHotlink(['yourdomain.com', 'beta.yourdomain.com']);
$anti_hotlink->allow_empty_referer = true;
$anti_hotlink->check();
?>

Token 验证防盗链

<?php
class TokenAntiHotlink {
    private $secret_key;
    private $expire_time = 3600; // 有效期1小时
    public function __construct($secret_key) {
        $this->secret_key = $secret_key;
    }
    // 生成访问令牌
    public function generateToken($resource_id, $timestamp = null) {
        $timestamp = $timestamp ?? time();
        $data = "{$resource_id}|{$timestamp}";
        $token = hash_hmac('sha256', $data, $this->secret_key);
        $token_data = base64_encode($data . '|' . $token);
        return $token_data;
    }
    // 验证令牌
    public function validateToken($token_data) {
        try {
            $decoded = base64_decode($token_data);
            if (!$decoded) return false;
            // 分割数据和签名
            $parts = explode('|', $decoded);
            if (count($parts) != 3) return false;
            list($resource_id, $timestamp, $token) = $parts;
            // 检查过期时间
            if ((time() - $timestamp) > $this->expire_time) {
                return false;
            }
            // 验证签名
            $expected_token = hash_hmac('sha256', "{$resource_id}|{$timestamp}", $this->secret_key);
            return hash_equals($expected_token, $token);
        } catch (Exception $e) {
            return false;
        }
    }
}
// 使用示例
$secret_key = 'your-secret-key-here';
$token_handler = new TokenAntiHotlink($secret_key);
// 生成带令牌的URL
$_GET['token'] = $token_handler->generateToken($_GET['file'] ?? 'image.jpg');
// 验证令牌
if (!$token_handler->validateToken($_GET['token'])) {
    header('HTTP/1.1 403 Forbidden');
    exit('Invalid or expired token');
}
?>

Apache/Nginx 层防盗链

Apache .htaccess

# Apache 防盗链配置
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourdomain\.com/.*$ [NC]
RewriteCond %{HTTP_REFERER} !^https://(www\.)?yourdomain\.com/.*$ [NC]
RewriteRule .*\.(jpg|jpeg|png|gif|bmp|mp3|mp4|flv)$ - [F,NC]
# 或者返回指定图片
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourdomain\.com/.*$ [NC]
RewriteRule .*\.(jpg|jpeg|png|gif|bmp)$ /images/hotlink.gif [L]
# 允许某些特定站点
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?yourdomain\.com/.*$ [NC]
RewriteCond %{HTTP_REFERER} !^http://(www\.)?google\.com/.*$ [NC]
RewriteRule .*\.(jpg|jpeg|png|gif|bmp)$ /images/hotlink.gif [L]

Nginx 配置

# Nginx 防盗链配置
location ~* \.(jpg|jpeg|png|gif|bmp|ico)$ {
    valid_referers none blocked server_names 
        *.yourdomain.com yourdomain.com;
    if ($invalid_referer) {
        return 403;
        # 或者返回指定图片
        # rewrite ^.*$ /images/hotlink.gif;
    }
    expires 30d;
    add_header Cache-Control "public, no-transform";
}
# 允许特定目录
location ^~ /uploads/ {
    valid_referers none blocked server_names 
        *.yourdomain.com yourdomain.com;
    if ($invalid_referer) {
        return 403;
    }
}

响应式错误信息

<?php
function antiHotlinkResponse($type = 'image') {
    $config = [
        'image' => [
            'content_type' => 'image/svg+xml',
            'message' => '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200"><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="24" font-family="Arial" fill="red">Access Denied</text></svg>'
        ],
        'video' => [
            'content_type' => 'text/html',
            'message' => '<!DOCTYPE html><html><body style="background:black;color:white;display:flex;align-items:center;justify-content:center;height:100vh;font-family:Arial;"><div><h1>🚫 Access Denied</h1><p>Please visit our website directly.</p></div></body></html>'
        ],
        'audio' => [
            'content_type' => 'text/plain',
            'message' => 'Audio access denied. Please use official player.'
        ],
        'json' => [
            'content_type' => 'application/json',
            'message' => json_encode(['error' => 'Access Denied', 'code' => 403])
        ]
    ];
    $typeConfig = $config[$type] ?? $config['image'];
    header('HTTP/1.1 403 Forbidden');
    header('Content-Type: ' . $typeConfig['content_type']);
    echo $typeConfig['message'];
    exit;
}
类型调用
$file_type = strtolower(pathinfo($_SERVER['REQUEST_URI'], PATHINFO_EXTENSION));
if (in_array($file_type, ['jpg', 'png', 'gif', 'webp'])) {
    antiHotlinkResponse('image');
} elseif (in_array($file_type, ['mp4', 'avi', 'mkv'])) {
    antiHotlinkResponse('video');
} elseif ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') {
    antiHotlinkResponse('json');
}
?>

完整示例:综合防盗链类

<?php
class HotlinkProtection {
    private $config;
    private $cache_prefix = 'hotlink_';
    public function __construct($config = []) {
        $this->config = array_merge([
            'allowed_domains' => [],
            'allow_empty' => true,
            'allow_localhost' => true,
            'exclude_ips' => [],
            'exclude_paths' => [],
            'error_image' => '/images/denied.png',
            'cache_time' => 3600
        ], $config);
    }
    public function protect($file_path) {
        // IP白名单检查
        if ($this->checkIpWhiteList()) {
            return true;
        }
        // 路径排除检查
        if ($this->checkExcludedPath()) {
            return true;
        }
        // Referer检查
        if ($this->checkReferer()) {
            return true;
        }
        // Token检查(如果有)
        if ($this->checkToken()) {
            return true;
        }
        // 防盗链响应
        $this->denyAccess();
        return false;
    }
    private function checkIpWhiteList() {
        $client_ip = $_SERVER['REMOTE_ADDR'] ?? '';
        return in_array($client_ip, $this->config['exclude_ips']);
    }
    private function checkExcludedPath() {
        $current_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        foreach ($this->config['exclude_paths'] as $path) {
            if (strpos($current_path, $path) === 0) {
                return true;
            }
        }
        return false;
    }
    private function checkReferer() {
        $referer = $_SERVER['HTTP_REFERER'] ?? '';
        if (empty($referer)) {
            return $this->config['allow_empty'];
        }
        $referer_host = parse_url($referer, PHP_URL_HOST);
        if ($this->config['allow_localhost']) {
            if ($referer_host === 'localhost' || $referer_host === '127.0.0.1') {
                return true;
            }
        }
        foreach ($this->config['allowed_domains'] as $domain) {
            if (preg_match('/\.' . preg_quote($domain, '/') . '$/', $referer_host)) {
                return true;
            }
        }
        return false;
    }
    private function checkToken() {
        if (isset($_GET['token'])) {
            // 实现简单的token验证逻辑
            $token_data = $_GET['token'];
            // 这里实现你的token验证
            return $this->validateToken($token_data);
        }
        return false;
    }
    private function validateToken($token) {
        // 简化示例
        $decoded = base64_decode($token);
        $expire = substr($decoded, -10);
        return (time() < $expire);
    }
    private function denyAccess() {
        // 设定缓存头
        header('Cache-Control: no-store, no-cache, must-revalidate');
        header('Cache-Control: post-check=0, pre-check=0', false);
        header('Pragma: no-cache');
        // 返回错误
        if (file_exists($this->config['error_image'])) {
            header('HTTP/1.1 403 Forbidden');
            header('Content-Type: ' . mime_content_type($this->config['error_image']));
            readfile($this->config['error_image']);
        } else {
            header('HTTP/1.1 403 Forbidden');
            header('Content-Type: text/plain');
            echo "Access Denied";
        }
        exit;
    }
}
// 使用示例
$protection = new HotlinkProtection([
    'allowed_domains' => ['yourdomain.com', 'sub.yourdomain.com'],
    'allow_empty' => true,
    'allow_localhost' => true,
    'exclude_ips' => ['192.168.1.1'],
    'exclude_paths' => ['/api/', '/admin/'],
    'error_image' => '/images/access_denied.png'
]);
$protection->protect(__DIR__ . '/protected_file.jpg');
?>

最佳实践建议

安全配置

<?php
// 安全header
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
// 使用HTTPS
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'on') {
    header("Location: https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
    exit;
}
?>

性能优化

<?php
// 缓存策略
header('Cache-Control: public, max-age=86400'); // 1天
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
header('ETag: "' . md5(file_get_contents($file_path)) . '"');
?>

补充说明

  • Referer伪造成的风险:HTTP_REFERER可以被伪造,重要资源建议使用Token方案
  • CDN配置:如果使用CDN,需要在CDN层面配置防盗链
  • 用户体验:对于误拦截情况提供清晰的错误提示和返回链接
  • 日志记录:记录防盗链拦截情况,便于分析
  • 定期更新:定期审查和更新允许域名列表

选择合适的方案时,要考虑:

  1. 资源重要性(图片/视频/API)
  2. 流量规模
  3. 是否需要被搜索引擎索引
  4. 是否需要支持CDN
  5. 误拦截的可接受程度

通常推荐组合策略:Nginx/Apache层处理静态资源,PHP层处理动态内容和API请求。

抱歉,评论功能暂时关闭!