本文目录导读:

我来详细讲解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层面配置防盗链
- 用户体验:对于误拦截情况提供清晰的错误提示和返回链接
- 日志记录:记录防盗链拦截情况,便于分析
- 定期更新:定期审查和更新允许域名列表
选择合适的方案时,要考虑:
- 资源重要性(图片/视频/API)
- 流量规模
- 是否需要被搜索引擎索引
- 是否需要支持CDN
- 误拦截的可接受程度
通常推荐组合策略:Nginx/Apache层处理静态资源,PHP层处理动态内容和API请求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播