НейроАгент

Как определить способ загрузки изображения в Nginx

Узнайте, как различать прямую загрузку изображений по URL и через тег <img> в Nginx. Оптимизируйте конфигурацию для эффективной обработки запросов.

Как определить, загружается ли картинка напрямую по URL или через тег на странице?

Мне нужно различать два способа загрузки изображений в браузер:

  1. Прямое обращение к файлу по URL (например: site/file/pic/poll/2025/10/16/500.jpg)
  2. Вставка изображения через тег в HTML-код страницы

Текущая конфигурация Nginx запускает PHP-скрипт file.php в обоих случаях, но мне нужно, чтобы он выполнялся только при прямом доступе к файлу. Как можно определить, каким образом была загружена картинка?

НейроАгент

Прямое обращение к изображению по URL и загрузка через тег различаются по HTTP-заголовкам запроса. Для определения способа загрузки можно использовать заголовок Referer, который отсутствует при прямом доступе к файлу, а также другие заголовки, такие как User-Agent и Accept. В Nginx это можно реализовать через модуль referer и проверку переменных запроса.


Содержание


Различие между прямым доступом и загрузкой через

При прямом доступе к изображению по URL (например, site/file/pic/poll/2025/10/16/500.jpg) браузер отправляет HTTP-запрос с определёнными характеристиками, которые отличаются от запроса, генерируемого тегом .

Ключевые различия в заголовках:

  1. Заголовок Referer:

    • При прямом доступе: отсутствует или содержит пустое значение
    • При загрузке через : содержит URL страницы, где расположен тег
  2. User-Agent:

    • Прямой доступ: содержит информацию о браузере пользователя
    • Загрузка через : аналогично, но может иметь некоторые отличия в контексте
  3. Accept:

    • Прямой доступ: обычно */* или конкретные типы изображений
    • Загрузка через : аналогично, но может отличаться в зависимости от контекста страницы

Методы определения способа загрузки в Nginx

Использование модуля referer

Основной метод для детекции прямого доступа - использование модуля referer в Nginx. Этот модуль позволяет проверять, содержит ли запрос корректный Referer.

Как это работает:

nginx
location ~* \.(jpg|jpeg|png|gif|webp)$ {
    valid_referers none blocked server_names *.yourdomain.com;
    
    if ($invalid_referer) {
        # Запрос пришел с некорректным Referer (прямой доступ)
        rewrite ^ /file.php?id=$request_uri last;
    }
    
    # Обычная обработка через PHP
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    include fastcgi_params;
}

Проверка переменных Nginx

Nginx предоставляет доступ к HTTP-заголовкам через переменные вида $http_<header_name>. Для детекции можно использовать:

  • $http_referer - значение заголовка Referer
  • $http_user_agent - значение User-Agent
  • $http_accept - значение Accept

Пример проверки:

nginx
if ($http_referer = "") {
    # Прямой доступ без Referer
    rewrite ^ /file.php?id=$request_uri last;
}

Конфигурация Nginx с использованием модуля referer

Базовая конфигурация

Для реализации требуемой функциональности можно использовать следующую конфигурацию Nginx:

nginx
server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/yourdomain.com;
    
    # Обработка изображений
    location ~* ^/file/pic/.*\.(jpg|jpeg|png|gif|webp)$ {
        # Разрешить доступ только с определённых Referer
        valid_referers none blocked server_names *.yourdomain.com;
        
        # Если Referer некорректный (прямой доступ)
        if ($invalid_referer) {
            return 301 /file.php?id=$request_uri;
        }
        
        # Обычная обработка изображения
        try_files $uri =404;
    }
    
    # Обработка PHP-скрипта
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Улучшенная конфигурация с регулярными выражениями

Для более точного определения пути к изображению можно использовать регулярные выражения:

nginx
location ~* "^/file/pic/(?<path>[^/]+)/.*\.(jpg|jpeg|png|gif|webp)$" {
    valid_referers none blocked server_names *.yourdomain.com;
    
    if ($invalid_referer) {
        return 301 /file.php?id=$path&file=$2;
    }
    
    try_files $uri =404;
}

PHP-код для детекции типа запроса

Получение информации о запросе

В PHP можно получить доступ к заголовкам через суперглобальный массив $_SERVER:

php
<?php
function isDirectImageAccess() {
    // Проверяем Referer
    $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
    
    // Проверяем User-Agent
    $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
    
    // Проверяем Accept
    $accept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : '';
    
    // Логика определения прямого доступа
    $isDirect = empty($referer) || 
                (strpos($accept, 'text/html') === false) ||
                (strpos($userAgent, 'curl') !== false) ||
                (strpos($userAgent, 'wget') !== false);
    
    return $isDirect;
}

// Пример использования
if (isDirectImageAccess()) {
    // Обработка прямого доступа
    $fileId = $_GET['id'] ?? '';
    // Ваша логика обработки...
    header('Content-Type: image/jpeg');
    readfile("/path/to/images/{$fileId}.jpg");
} else {
    // Обработка через <img> тег
    // Возвращаем изображение напрямую или перенаправляем
    header('Location: /path/to/default/image.jpg');
}
?>

Улучшенная PHP-функция с дополнительной проверкой

php
<?php
function detectImageAccessMethod() {
    $headers = getallheaders();
    $referer = $headers['Referer'] ?? '';
    $userAgent = $headers['User-Agent'] ?? '';
    $accept = $headers['Accept'] ?? '';
    
    // Признаки прямого доступа
    $directAccessIndicators = [
        empty($referer),
        strpos($accept, 'text/html') === false,
        strpos($userAgent, 'curl') !== false,
        strpos($userAgent, 'wget') !== false,
        strpos($userAgent, 'Postman') !== false,
        strpos($userAgent, 'Python-urllib') !== false
    ];
    
    // Если есть хотя бы один признак прямого доступа
    return in_array(true, $directAccessIndicators, true);
}

// Пример использования
if (detectImageAccessMethod() && isset($_GET['id'])) {
    // Обработка прямого доступа
    handleDirectImageAccess($_GET['id']);
} else {
    // Обработка через <img>
    handleImageViaTag();
}
?>

Альтернативные подходы и ограничения

Ограничения метода с referer

  1. Надёжность: Referer можно подделать или отключить в браузере
  2. Безопасность: Некоторые браузеры и расширения могут блокировать передачу Referer
  3. Производительность: Проверка Referer добавляет небольшие накладные расходы

Альтернативные методы

1. Использование JavaScript для установки заголовка

javascript
// На странице с изображениями
document.querySelectorAll('img[data-image-id]').forEach(img => {
    img.addEventListener('load', function() {
        fetch('/track-image-access.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Image-Access': 'img-tag'
            },
            body: JSON.stringify({
                imageId: this.dataset.imageId
            })
        });
    });
});

2. Использование сессий и cookies

php
<?php
// В PHP-скрипте, генерирующем страницу
session_start();
$_SESSION['page_with_images'] = true;

// В обработчике изображений
function isFromImageTag() {
    return isset($_SESSION['page_with_images']) && $_SESSION['page_with_images'];
}
?>

3. Обработка через отдельный эндпоинт

nginx
location ~* ^/file/pic/.*\.(jpg|jpeg|png|gif|webp)$ {
    # Перенаправляем на PHP-обработчик для всех запросов
    rewrite ^ /image-handler.php?path=$request_uri last;
}

location = /image-handler.php {
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root/image-handler.php;
}

Практическая реализация

Комплексное решение

Вот пример комплексной конфигурации Nginx и PHP для решения задачи:

Nginx конфигурация:

nginx
server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/yourdomain.com;
    
    # Обработка изображений с проверкой Referer
    location ~* "^/file/pic/(?<image_path>[^/]+)/.*\.(jpg|jpeg|png|gif|webp)$" {
        # Проверяем Referer
        valid_referers none blocked server_names *.yourdomain.com;
        
        if ($invalid_referer) {
            # Прямой доступ - перенаправляем на PHP-обработчик
            return 301 /file.php?image=$image_path&direct=1;
        }
        
        # Обычная обработка изображения
        try_files /images/$image_path.$2 =404;
    }
    
    # Обработка PHP-скриптов
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

PHP обработчик:

php
<?php
// file.php
function handleImageRequest() {
    $imagePath = $_GET['image'] ?? '';
    $isDirect = isset($_GET['direct']) && $_GET['direct'] === '1';
    
    if ($isDirect) {
        // Обработка прямого доступа
        $imageFile = "/var/www/yourdomain.com/images/{$imagePath}.jpg";
        
        if (file_exists($imageFile)) {
            // Можно добавить логирование или дополнительную обработку
            logDirectAccess($imagePath);
            
            header('Content-Type: image/jpeg');
            header('Content-Length: ' . filesize($imageFile));
            readfile($imageFile);
            exit;
        } else {
            http_response_code(404);
            echo "Image not found";
            exit;
        }
    } else {
        // Обработка через <img> тег
        header('Location: /images/' . $imagePath . '.jpg');
        exit;
    }
}

function logDirectAccess($imagePath) {
    $logFile = '/var/log/nginx/direct_access.log';
    $timestamp = date('Y-m-d H:i:s');
    $ip = $_SERVER['REMOTE_ADDR'];
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
    
    $logEntry = "[$timestamp] Direct access to: $imagePath | IP: $ip | UA: $userAgent\n";
    
    file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
}

// Запускаем обработку
handleImageRequest();
?>

Дополнительные рекомендации

  1. Кэширование: Добавьте заголовки кэширования для изображений
  2. Безопасность: Проверяйте права доступа к файлам
  3. Логирование: Ведите логи прямых обращений для анализа
  4. Защита: Ограничьте частоту запросов для предотвращения злоупотреблений
nginx
# Добавление заголовков кэширования
location ~* \.(jpg|jpeg|png|gif|webp)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    
    # ... остальная конфигурация
}

Источники

  1. How to Prevent Direct Access to Images in NGINX - Fedingo
  2. How to redirect user if direct access image files by browser? [nginx] - Server Fault
  3. Nginx: Prevent direct access to static files - Stack Overflow
  4. Prevent nginx from serving content to external domains (hotlinking) - Claud.io
  5. How to redirect if user direct access images with nginx? — LowEndTalk
  6. Managing request headers | NGINX
  7. nginx blocking direct access images - TechExpert
  8. nginx.org - ngx_http_referer_module
  9. Servers for Hackers - Mapping Headers in Nginx
  10. O’Reilly - Request headers - Nginx HTTP Server

Заключение

Для определения способа загрузки изображения (прямой доступ через URL или через тег ) можно использовать следующие подходы:

  1. Основной метод: Используйте модуль referer в Nginx для проверки заголовка Referer. При прямом доступе этот заголовок отсутствует или содержит некорректное значение.

  2. Дополнительные проверки: Комбинируйте проверку Referer с анализом других заголовков (User-Agent, Accept) для повышения точности детекции.

  3. PHP-обработка: В PHP-скрипте проверяйте заголовки через $_SERVER суперглобальный массив и реализуйте различную логику обработки в зависимости от типа запроса.

  4. Безопасность: Учитывайте, что Referer можно подделать, поэтому всегда добавляйте дополнительные проверки и валидацию.

  5. Производительность: Оптимизируйте конфигурацию Nginx, добавляя кэширование и минимизируя количество проверок для обычных запросов через теги.

Реализация предложенных методов позволит вам эффективно различать два способа загрузки изображений и запускать PHP-скрипт только при прямом доступе к файлам.