Как сделать одноразовую ссылку на файл

Обновлено: 07.07.2024

Одноразовые ссылки можно использовать в самых разных ситуациях: для того, чтобы предоставить временный доступ к файлу или странице, или для подтверждения регистрации. В этом уроке мы покажем, как сгенерировать и внедрить одноразовые URL адреса.

Создание URL

Предположим, что у нас на сайте есть система аутентификации пользователей. После регистрации мы просим пользователя пройти процедуру верификации email-а. Для создания подобных ссылок можем воспользоваться специальным параметром token. Пример подобной ссылки:

Без базы данных тут нам не обойтись, поэтому давайте посмотрим на таблицу, с которой будем работать.

В таблице будем хранить 3 поля: токен, имя пользователя и время. Для генерации токена мы воспользуемся функцией sh1(), которая выдаёт строку из 40 символов. Поле tstamp будет хранить время генерации токена для того, чтобы мы могли отследить ссылки с истёкшим сроком.

Существует множество способов генерации токена, однако в этом уроке мы воспользуемся функциями uniqid() и sh1(). Независимо от способа генерации токена, убедитесь что генерируемые значения будут разными и вероятность появления дубликатов минимальна.

В качестве параметра функция uniqid() принимает строку, а на выходе даёт уникальный идентификатор, основанный на переданном аргументе и текущем времени. Также, в качестве второго аргумента, данная функция принимает булево значение, которое даст сигнал uniqid прибавить несколько дополнительных символов для увеличения вероятности уникальности значения. Функция sh1 принимает уникальный идентификатор и создаёт хэш.

После работы данных двух функций, у нас будет уникальный токен, который можем использовать для генерации url адресов. Теперь нам нужно занести его в базу:

Для того чтобы мы знали, какого пользователя следует активировать, в таблицу будем записывать и логин пользователя. В примере, более адаптированном для реального сайта, можете воспользоваться ID пользователя.

Теперь, когда у нас есть вся необходимая информация, можем создать временный url адрес:

Проверка

Теперь нам нужен скрипт, благодаря которому мы будем осуществлять проверку. Всё что нам нужно сделать, это сравнить токен из url адреса и токен из базы. Если такой имеется, и время его жизни не истекло, то всё ОК.

Также нам нужно предусмотреть проверку токенов, время жизни которых истекло:

Таким образом, у нас будут осуществляться две проверки: одна на валидность токена, другая на время его существования.

Данный метод можно применить не только для активации учётных записей пользователей, но и в других нуждах: к примеру, для предоставления однократного или временного доступа к какому-то ресурсу или услуге.

Вдобавок ко всему этому, вы можете создать скрипт, который будет удалять токены, которыми ни разу не пользовались. Данный скрипт можно запускать самим время от времени или применить для этого cron.

Предполагается, что у вас уже есть настроенный nginx в такой же «комплектации», следовательно, ниже будет описываться лишь настройка нескольких location, которые вы добавите в свою секцию server конфига nginx.

В моём случае все временные файлы будут храниться в папке /var/www/upload по пути вида /random_folder_name/filename, где в качестве random_folder_name будет рандомная строка из нужного нам количества байт, потому создаём location вида:

Проверяем, что загрузка и удаление файлов и папок работает командами в консоли


Для того, чтобы оградить свой сервер от неконтролируемого потока загружаемых файлов, добавим проверку токена, который мы будем передавать заголовком Token. В конфиге это будет выглядеть следующим образом

Сам токен можно сгенерировать в консоли командой вида

Снова проверяем, командами в консоли, что загрузка и удаление файлов и папок работает, но только при наличии в запросе заголовка Token


Загружать и удалять файлы мы научились, но для того, чтобы скачивать файлы мы заведём отдельный location

Проверяем, что получение файлов работает командой в консоли

  • Если единожды попросить у nginx файл, то он его закеширует и будет снова и снова его отдавать, даже если файл удалить с диска. Это не укладывается в нашу концепцию одноразовых ссылок, потому необходимо, следуя инструкции привести директиву open_file_cache к значению off
  • Для того, чтобы все файлы отдавались как аттачи, в том числе и html, необходимо их отдавать с заголовками Content-Type: application/octet-stream и Content-Disposition: attachment. А также, чтобы «умные» браузеры, например Internet Explorer, не могли переопределить content type на основе содержимого файла, нужен заголовок X-Content-Type-Options: nosniff. В конфиге это будет выглядеть следующим образом

Теперь мы научились загружать и безопасно получать, но нам нужно сделать так, чтобы они удалялись сразу после скачивания, а для этого мы заведём отдельный location

И вызывать этот location мы будем из location

Выглядит вполне прилично, но я, как и писал выше, крайне не рекомендую использовать то, что непонятно как работает и не задокументировано. Именно поэтому я надеюсь, что никто не будет использовать это решение «в бою»

Теперь всё хорошо, ибо файлы мы можем загрузить, скачать, и после скачивания они удаляются, но полученные ссылки невозможно передавать в мессенджерах, т.к. боты делают запросы по этим ссылкам в надежде получить контент и сгенерировать превью, что приводит к тому, что файл сразу же удаляется, а получатель при переходе по ссылке наблюдает 404 вместо заветного файла.
Для решения этой проблемы мы воспользуемся тем, что будем отправлять получателю не прямую ссылку на скачивание файла, а ссылку на промежуточную страницу, и сделаем это также только благодаря возможностям «коробочного» Nginx.
Первым делом создаём ещё один location, который будет отдавать html-файл

^/download/… проверку заголовка Referer, чтобы файл отдавался только в том случае, если он действительно был скачан с промежуточной страницы


Итоговый конфиг в моём случае выглядит следующим образом


Чтобы теперь этим было удобно пользоваться и не вбивать в консоли длинные команды для загрузки файлов и папок, я набросал в .zshrc (предполагаю, что будет работать и в .bashrc)

  • Использование недокументированной директивы post_action, которую использовать нельзя
  • Нет докачки. Если оборвалось соединение, то nginx исполнит директиву post_action и удалит файл
  • Всё это выглядит как магия

UPD: Статья обновлена 18.01.2018. Всем, кто ранее успел настроить подобное у себя, настоятельно рекомендую внести соответствующие изменения, руководствуясь обновлённой статьёй.

Если вам нужно сделать ссылку на скачивание, но при этом доступ был ограничен по времени или количеству переходов, то это можно сделать в пару кликов.

Пошаговая инструкция, как сделать одноразовую ссылку:

  1. В поле с доменом вставьте реальную вашу ссылку, которую нужно обернуть в одноразовую.
  2. Выберете срок действия ссылки: 1 час, 1 неделя, 1 месяц, без ограничений по времени.
  3. Далее укажите в какой момент перестанет готовая ссылка работать: 1 переход, 5 переходов, неограниченная.
  4. Укажите количество копий – другими словами, сколько вам нужно одноразовых ссылок. Чтобы не по одной постоянно генерировать, а сразу несколько.
  5. Нажимаем «получить ссылку»

С помощью сервиса можно генерировать не только временные ссылки, но и просто сокращать длину.

Например, вы хотите длинную ссылку Google Excel преобразовать в короткую. Алгоритм следующий:

  1. Вставляете ссылку Google Excel в поле домен
  2. Указываете «Срок действия – без ограничений по времени»
  3. Тип короткой ссылки – неограниченная
  4. Нажимаем кнопку «Получить ссылку»
  5. Всё! У вас короткая ссылка, которая работает вечно

Сделать одноразовую ссылку на несколько доменов

Справа от поля куда вставляется домен, есть плюсик «+», нажимая на него выпадет еще одно поле с доменом.

Таким образом, если вам нужно сделать одноразовые или короткие ссылки на разные домены, то это можно реализовать также в пару кликов.

Кому обычно полезен сервис

  1. Тем, кто хочет выдать временную ссылку на скачивание файла
  2. Добавить закрытый чат-телеграмм по одноразовой ссылке
  3. Выдать один раз доступ к фото и т.д.d

Другими словами, чтобы пользователи не распространяли и не делились постоянными ссылками на ваш ресурс.

PS: у нас есть сервис BotPay.me который сделан конкретно под закрытые Телеграм-каналы по ежемесячной подписке.

Вы делитесь контентом, клиенты ежемесячно платят на ваш счет, BotPay автоматизирует весь процесс.

скачать исходники
скачать урок

1. Постановка задачи

Для начала давайте условимся, что одноразовую ссылку мы будем создавать для скачивания файла. Данный файл будем хранить в папке file и это обычное изображение, которое необходимо для примера.

Далее, создание одноразовой ссылки заключается в генерации уникальной строки, которую необходимо передать в специальный файл обработчик. Данный файл выполнит необходимые проверки и если ссылка верна (уникальная строка), значит, откроется доступ для скачивания файла. Конечно, для каждого пользователя необходимо создавать отдельную ссылку, и поэтому все созданные ссылки необходимо где то хранить. В качестве хранилища ссылок можно выбрать или базу данных, или обычный текстовый файл. Мы в данном уроке будем использовать текстовый файл для хранения созданных ссылок.


Бесплатный курс по PHP программированию

Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC

В курсе 39 уроков | 15 часов видео | исходники для каждого урока

И последнее, необходимо обеспечить одноразовое использование ссылки. То есть, любую созданную ссылку, пользователь сможет использовать не более одного раза. При повторном использовании ссылки, доступ на скачивание файла необходимо запретить. Теперь давайте приступим к реализации поставленной задачи.

2. Создание одноразовых ссылок

Итак, на локальном компьютере в папке lessons, я создал папку onetime, в которой мы будем хранить наш будущий скрипт. То есть путь к папке с скриптом будет следующий: //localhost/lessons/onetime/

Теперь, давайте создадим новый файл, под названием get_hash.php, который будет генерировать уникальную строку и тем самым формировать одноразовую ссылку, для скачивания файла. Первым делом, генерируем уникальную строку:


Как Вы видите, строка успешно генерируется. Далее, создадим переменную $file, в которой будем хранить имя файла, в котором будут храниться созданные уникальные строки для ссылок:

Теперь, созданную строку необходимо сохранить в файле, поэтому, давайте откроем текстовый файл для записи:

Для открытия файла для записи используем функцию fopen(). Которая открывает файл в определенном режиме. Мы будем работать в режиме a, то есть, открываем файл только для записи и указатель перемещаем в конец файла (если мы вызываем функцию в первый раз, то открываемый файл будет создан).

Теперь, для предотвращения появления ошибок, связанных с постоянным обращением к файлу, необходимо блокировать его на время записи данных. То есть при генерации ссылки, выполняется запись уникальной строки в файл. Затем, мы с Вами будем постоянно считывать данные файла, для проверки правильности строк. При этом могут возникать ошибки при многочисленных обращениях к файлу в один и тот же момент времени (то есть запись и считывание данных происходит практически одновременно). Поэтому перед записью данных мы с Вами заблокируем файл функцией flock():

Напомню, что данная функция накладывает блокировку на файл. Режим работы которой, задается константами, передаваемыми вторым параметром к данной функции. Режим LOCK_EX – накладывает эксклюзивную блокировку (запись). И при этом все возможные обращения файлу будут запрещены, то есть пока он заблокирован. Кроме записи данных в файл из нашего скрипта, что собственно мы и выполним:

Для записи мы используем функцию fwrite, и записываем сгенерированную уникальную строку. Теперь давайте разблокируем файл, то есть, разрешим обращение к нему из вне, и закроем файл:

Для разблокировки доступа к файлу, мы используем функцию flock(), и передаем константу LOCK_UN, то есть снятие блокировок. Теперь, давайте несколько раз обновим скрипт и посмотрим, что записалось в файл:


Как Вы видите, данные в файл успешно записаны. Теперь необходимо сформировать ссылку и отобразить ее на экране для пользователя:

$ path = substr ( $ _SERVER [ 'PHP_SELF' ] , 0 , strrpos ( $ _SERVER [ 'PHP_SELF' ] , "/" ) ) ; echo "<a href='//" . $ _SERVER [ 'HTTP_HOST' ] . $ path . "/get_file.php?hash crayon-sy">. $ hash . "'>//" . $ _SERVER [ 'HTTP_HOST' ] . $ path . "/get_file.php?hash crayon-sy">. $ hash . "</a>" ;


Как Вы видите, ссылка выводится, уникальная строка генерируется и записывается в текстовый файл. Значит, мы все сделали правильно. Теперь на всякий случай приведу полный код файла, get_hash.php:

$path = substr ( $_SERVER [ 'PHP_SELF' ] , 0 , strrpos ( $_SERVER [ 'PHP_SELF' ] , "/" ) ) ; echo "<a href='//" . $_SERVER [ 'HTTP_HOST' ] . $path . "/get_file.php?hash crayon-sy">. $hash . "'>//" . $_SERVER [ 'HTTP_HOST' ] . $path . "/get_file.php?hash crayon-sy">. $hash . "</a>" ;

Теперь необходимо создать скрипт, который получит передаваемые данные и выполнит необходимые проверки.


Бесплатный курс по PHP программированию

Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC

В курсе 39 уроков | 15 часов видео | исходники для каждого урока

3. Проверка ссылок

Итак, создаем пустой файл get_file.php, в котором мы будем проверять правильность ссылки и если все верно, предоставлять доступ для скачивания файла. Первым делом, создаем две переменные, которые будут содержать имя файла для скачивания (ссылку на который мы создаем), и имя файла с уникальными строками ссылок:

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

Данный в этот файл передаются методом GET (через адресную строку), значит в суперглобальном массиве GET, будет создана ячейка hash. Поэтому, сохраним значение данной ячейки, так же в отдельной переменной:

Теперь, так как мы знаем, что уникальная строка, получена с помощью шифрования md5, значит уникальная строка должна содержать ровно 32 символа. Поэтому, давайте выполним первую проверку:

Теперь считаем файл с уникальными строками и все его содержимое построчно, сохраним в ячейках массива:

То есть массив $arr, содержит в каждой своей ячейке сгенерированную строку из файла. К примеру, если распечатать его функцией print_r(), мы увидим следующее:


Теперь, так как все содержимое файла, содержится в одном массиве, мы можем проверить, есть ли строка, переданная через GET параметры (значение переменной $hash) в одной из ячеек массива $arr. И если действительно есть совпадение, значит, доступ к файлу можно открыть.

Но, нам ведь еще нужно обеспечить одноразовое использование каждой ссылки. Поэтому, мы очистим файл с уникальными строками. Затем в цикле обойдем массив $arr и на каждой итерации цикла будем сравнивать текущее значение ячейки массива, с значением переменной $hash. Если совпадение не найдено, значение данной ячейки запишем обратно в файл, если же найдено совпадение, то записывать ничего не будем, только в переменную $check сохраним значение TRUE. При этом мы обеспечим одноразовое использование ссылки. Так как если ссылка пройдет проверку (будет найдено совпадение с кодом файла), ее уникальный код при перезаписи уже не попадет в текстовый файл. И повторно использовать данную ссылку (уникальный код для проверки) будет не возможно.

Поэтому, открываем файл для записи в режиме w (открывает файл только для записи и помещает указатель в начало файла и обрезает файл до нулевой длины.):

Читайте также: