Реализация генерации уникальных ссылок на скачивание

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация генерации уникальных ссылок на скачивание
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация генерации уникальных ссылок на скачивание

Уникальная ссылка — основной механизм контроля доступа к цифровым товарам. В отличие от прямой ссылки на файл, уникальный токен не позволяет угадать URL другой покупки и привязывает скачивание к конкретной транзакции. Правильная реализация требует криптографически стойкого генератора, защиты от перебора и механизма ротации компрометированных ссылок.

Требования к токену

  • Непредсказуемость — нельзя угадать или вывести из порядкового номера заказа
  • Достаточная энтропия — минимум 128 бит (32 байта случайных данных → 64 символа hex)
  • Уникальность — в таблице UNIQUE-ограничение с обработкой коллизий
  • Короткий URL — токен не должен быть длиннее 64–80 символов

Генерация токена

class DownloadTokenGenerator
{
    /**
     * Генерируем 32 случайных байта через CSPRNG и конвертируем в hex
     * random_bytes() использует /dev/urandom на Linux, CryptGenRandom на Windows
     */
    public function generate(): string
    {
        return bin2hex(random_bytes(32)); // 64 символа
    }

    /**
     * Генерация с гарантией уникальности
     */
    public function generateUnique(int $maxAttempts = 5): string
    {
        for ($i = 0; $i < $maxAttempts; $i++) {
            $token = $this->generate();

            if (!DigitalOrderDownload::where('token', $token)->exists()) {
                return $token;
            }
        }

        // Коллизия 5 раз подряд при 64-символьном hex — статистически невозможна,
        // но обрабатываем как hard error
        throw new TokenGenerationFailedException('Не удалось сгенерировать уникальный токен');
    }
}

Короткие токены (опционально)

Если нужны читаемые ссылки (для SMS, QR-кодов), используется Base62:

class ShortTokenGenerator
{
    private const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

    /**
     * 16 символов Base62 = ~95 бит энтропии — достаточно для ссылок скачивания
     */
    public function generate(int $length = 16): string
    {
        $token = '';
        $bytes = random_bytes($length);

        for ($i = 0; $i < $length; $i++) {
            $token .= self::ALPHABET[ord($bytes[$i]) % 62];
        }

        return $token;
    }
}

Структура URL

https://example.com/dl/a3f8c92b1d4e7f0a2b5c8d9e1f3g4h5j

Короткий путь /dl/ — не /download/ — чтобы не раскрывать логику системы. Токен в пути, не в query string — query string логируется в access_log и может попасть в рефереры.

// routes/web.php
Route::get('/dl/{token}', [DigitalDownloadController::class, 'show'])
    ->name('digital.download.show')
    ->middleware(['throttle:30,1']); // максимум 30 запросов в минуту

Route::get('/dl/{token}/get', [DigitalDownloadController::class, 'download'])
    ->name('digital.download.get')
    ->middleware(['throttle:10,1']); // реальное скачивание — жёстче

Rate limiting против перебора

// app/Http/Middleware/ThrottleDownloadTokens.php
class ThrottleDownloadAttempts
{
    public function handle(Request $request, Closure $next): Response
    {
        $key = 'download-attempt:' . $request->ip();

        if (RateLimiter::tooManyAttempts($key, 20)) {
            // Слишком много попыток с одного IP
            abort(429, 'Слишком много запросов. Попробуйте позже.');
        }

        RateLimiter::hit($key, 60); // окно 1 минута

        $response = $next($request);

        // Неверный токен (404) тоже считается попыткой
        if ($response->getStatusCode() === 404) {
            RateLimiter::hit('invalid-token:' . $request->ip(), 3600);

            if (RateLimiter::tooManyAttempts('invalid-token:' . $request->ip(), 10)) {
                // Более 10 несуществующих токенов с одного IP за час — блокируем
                app(IpBlocklistService::class)->block($request->ip(), reason: 'token_bruteforce');
            }
        }

        return $response;
    }
}

Ротация скомпрометированной ссылки

Если покупатель сообщает, что ссылка была опубликована или украдена:

class RotateDownloadTokenAction
{
    public function execute(DigitalOrderDownload $download, string $reason): DigitalOrderDownload
    {
        // Инвалидируем старый токен
        $download->update(['is_revoked' => true]);

        // Логируем причину
        DownloadTokenRotationLog::create([
            'original_token' => $download->token,
            'reason'         => $reason,
            'rotated_at'     => now(),
            'rotated_by'     => auth()->id(),
        ]);

        // Создаём новый токен с теми же параметрами
        $newDownload = DigitalOrderDownload::create([
            'order_item_id'      => $download->order_item_id,
            'digital_product_id' => $download->digital_product_id,
            'token'              => app(DownloadTokenGenerator::class)->generateUnique(),
            'downloads_count'    => 0, // счётчик сбрасывается
            'downloads_limit'    => $download->downloads_limit,
            'expires_at'         => $download->expires_at,
        ]);

        // Отправляем новую ссылку
        Mail::to($download->orderItem->order->email)
            ->send(new DownloadTokenRotatedMail($newDownload));

        return $newDownload;
    }
}

Подписанные URL (альтернативный подход)

Вместо хранения токена в БД можно использовать HMAC-подписанные URL. Преимущество — не нужна запись в БД для каждой покупки. Недостаток — нельзя инвалидировать отдельный токен без ключа.

// Генерация подписанного URL с истечением
$url = URL::temporarySignedRoute(
    'digital.download.get',
    now()->addDays(30),
    ['order_item_id' => $item->id]
);

// Проверка в контроллере — автоматически через middleware SignedMiddleware
Route::get('/dl/signed/{order_item_id}', [DigitalDownloadController::class, 'signedDownload'])
    ->name('digital.download.get')
    ->middleware('signed');

Подписанные URL используются, когда нужно раздать доступ сразу многим пользователям (например, корпоративная лицензия на 100 сотрудников) без создания 100 записей в БД.

Мониторинг аномалий

// Алерт: один токен скачивается с множества IP
$suspiciousDownloads = DownloadEvent::selectRaw('
    digital_order_download_id,
    COUNT(DISTINCT ip_address) as unique_ips,
    COUNT(*) as total_downloads
')
->where('downloaded_at', '>', now()->subHours(24))
->groupBy('digital_order_download_id')
->having('unique_ips', '>', 5)
->get();

Сроки

Базовая генерация токенов + защита от перебора — 1–2 рабочих дня. Ротация токенов, мониторинг аномалий, подписанные URL как альтернативный механизм — ещё 2 дня.