Настройка защиты изображений от копирования 1С-Битрикс
Конкуренты копируют фотографии товаров с водяным знаком компании — это происходит постоянно. Полностью остановить загрузку изображений невозможно, но можно поднять стоимость копирования до уровня, когда проще снять свои фотографии.
Что реально работает, что нет
Не работает:
- Запрет правой кнопки мыши через JavaScript (
oncontextmenu="return false") — отключается в браузере за 3 секунды - CSS
pointer-events: none— изображение всё равно доступно через DevTools - Запрет сохранения через JS-обработчик
dragstart— копируется через скриншот или сетевую вкладку
Работает:
- Водяной знак, нанесённый на изображение на сервере — сложно убрать, если знак полупрозрачный и расположен в центре
- Отдача изображений через PHP с проверкой реферера — замедляет хотлинкинг
- Подмена URL изображений на ресурс с авторизацией
Водяной знак через GD
Наиболее практичный подход — нанесение водяного знака при загрузке изображения в Битрикс. Обработчик события OnAfterFileSave:
// /local/php_interface/init.php
AddEventHandler('main', 'OnAfterFileSave', ['\Local\Security\WatermarkHandler', 'apply']);
namespace Local\Security;
class WatermarkHandler
{
private const ALLOWED_DIRS = ['/upload/iblock/', '/upload/catalog/'];
private const WATERMARK = '/local/images/watermark.png';
public static function apply(array $file): void
{
$path = $file['PATH'] ?? '';
// Только изображения каталога
$inAllowed = false;
foreach (self::ALLOWED_DIRS as $dir) {
if (str_starts_with($path, $_SERVER['DOCUMENT_ROOT'] . $dir)) {
$inAllowed = true;
break;
}
}
if (!$inAllowed) return;
if (!in_array(strtolower($file['CONTENT_TYPE'] ?? ''), ['image/jpeg', 'image/png', 'image/webp'])) return;
if (!file_exists($path) || !file_exists($_SERVER['DOCUMENT_ROOT'] . self::WATERMARK)) return;
self::applyWatermark($path, $_SERVER['DOCUMENT_ROOT'] . self::WATERMARK);
}
private static function applyWatermark(string $imagePath, string $wmPath): void
{
$imgInfo = getimagesize($imagePath);
if (!$imgInfo) return;
// Загружаем исходное изображение
$image = match ($imgInfo[2]) {
IMAGETYPE_JPEG => imagecreatefromjpeg($imagePath),
IMAGETYPE_PNG => imagecreatefrompng($imagePath),
default => null,
};
if (!$image) return;
$wm = imagecreatefrompng($wmPath);
$imgW = imagesx($image);
$imgH = imagesy($image);
$wmW = imagesx($wm);
$wmH = imagesy($wm);
// Масштабируем знак до 30% ширины изображения, если он крупнее
if ($wmW > $imgW * 0.3) {
$ratio = ($imgW * 0.3) / $wmW;
$newWmW = (int)($wmW * $ratio);
$newWmH = (int)($wmH * $ratio);
$resizedWm = imagecreatetruecolor($newWmW, $newWmH);
imagealphablending($resizedWm, false);
imagesavealpha($resizedWm, true);
imagecopyresampled($resizedWm, $wm, 0, 0, 0, 0, $newWmW, $newWmH, $wmW, $wmH);
imagedestroy($wm);
$wm = $resizedWm;
$wmW = $newWmW;
$wmH = $newWmH;
}
// Позиция: центр изображения
$dstX = (int)(($imgW - $wmW) / 2);
$dstY = (int)(($imgH - $wmH) / 2);
imagecopy($image, $wm, $dstX, $dstY, 0, 0, $wmW, $wmH);
// Сохраняем обратно
match ($imgInfo[2]) {
IMAGETYPE_JPEG => imagejpeg($image, $imagePath, 90),
IMAGETYPE_PNG => imagepng($image, $imagePath, 7),
};
imagedestroy($image);
imagedestroy($wm);
}
}
Защита от хотлинкинга через nginx
location ~* \.(jpg|jpeg|png|gif|webp)$ {
valid_referers none blocked ~\.yourdomain\.com;
if ($invalid_referer) {
return 403;
}
}
Или подменяем изображение при хотлинкинге:
location ~* \.(jpg|jpeg|png|gif|webp)$ {
valid_referers none blocked ~\.yourdomain\.com;
if ($invalid_referer) {
rewrite ^ /local/images/hotlink-protected.jpg last;
}
}
Отдача изображений через PHP (для приватных каталогов)
Если изображения должны быть доступны только авторизованным пользователям:
// /local/ajax/secure-image.php
$fileId = (int)($_GET['id'] ?? 0);
$token = $_GET['token'] ?? '';
if (!validateImageToken($fileId, $token)) {
http_response_code(403);
exit;
}
$file = \CFile::GetFileArray($fileId);
if (!$file) { http_response_code(404); exit; }
$path = $_SERVER['DOCUMENT_ROOT'] . $file['SRC'];
if (!file_exists($path)) { http_response_code(404); exit; }
header('Content-Type: ' . $file['CONTENT_TYPE']);
header('Cache-Control: private, max-age=3600');
readfile($path);
Токен — HMAC от ID файла и соли: hash_hmac('sha256', $fileId, SECRET_KEY).
Практические рекомендации
Водяной знак работает в сочетании с грамотным дизайном: полупрозрачный логотип в центре сложнее убрать, чем угловой. Для ценных товаров (ювелирка, дизайнерская мебель) — наносить на 100% изображений. Для массового каталога с тысячами SKU — только на главное изображение товара. Изображения брендов от поставщиков водяным знаком не трогаем — контракты запрещают.







