Генерация токенов и их уникальность

Рейтинг: 0Ответов: 2Опубликовано: 22.04.2015

Многие сервисы, например, короткие ссылки, генерируют токен, по которому доступна твоя ссылка. Или если даже взять ютуб, тоже, видео доступны по токенам - ?v=AbbASdw.. Вопрос заключается в следующем: где гарантия, что при генерации такого рода токена, он не будет совпадать уже с существующем? Или они сначала проверяют? Какой алгоритм используется, что все токены получаются уникальными? Ведь по сути, это просто генерация строки. Каков процент совпадения?

Ответы

▲ 2

В своей системе мы храним такие токены в Redis-е. Код генерации и проверок примерно следующий:

class Token
{
  const TTL = 60;
  const KEY = 'token:%s';

  protected $redis;

  public function __construct(\Predis\Client $redis)
  {
    $this->redis = $redis;
  }

  public function generate()
  {
    do {
      $id = uniqid();
      $key = sprintf(static::KEY, $id);
    } while ($this->redis->exists($key));
    $this->redis->setex($key, static::TTL, true);

    return $id;
  }

  public function validate($token)
  {
    return $this->redis->exists(sprintf(static::TOKEN, $token));
  }
}

Метод generate создает новую ID-шку токена, проверяя, есть ли уже такой идентификатор в Redis-е. Как только создал, помечает его в редисе, что уже создал с временем жизни 60 секунд (после 60 секунд данный ключ автоматом удалится из Redis-а). Метод validate проверяет: создавался ли токен в течении 60 секунд с таким названием.

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

▲ 2

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

$hash = md5(rand(0, PHP_INT_MAX));

Хэш-функции служат для того, чтобы преобразовать некоторое значение произвольной длины в строку фиксированной длины, причем результат будет всегда повторятся для одинаковых input-значений (в данном примере это неважно). md5, например, всегда возвращает строку из 32 символов:

over      -> 3b759a9ca80234563d87672350659b2b
overflow  -> 0bd9f6dd716003f3818d15d2e211ee73
overflown -> 09d0a5b30b267c2504fadd43348fbba3

Таким образом можно сгенерировать эти символы (строку с хэшем можно обрезать до 6-7 символов, распределение символов на этом участке должно быть настолько же "случайным", как и на всей длине). Если применяется хэш-функция, то алгоритм создания хэша до безумия прост:

do {
    $hash = md5(rand(0, PHP_INT_MAX));
} while (hashExists($hash));
// while не успокоится, пока не будет сгенерирован несуществующий хэш.
// После этого он будет доступен в перменной $hash.
// Тут сразу же возникает проблема race conditions (грубо говоря - 
// многопоточности), но она лежит за пределами этого вопроса.

// Можно упростить до предела, хотя в некоторых местах за 
// неочевидный код бьют по пальцам
while (hashExists($hash = generateHash()));

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

Интереснее выглядит инкрементальный идентификатор. Каждый материал так или иначе оказывается в базе данных, и, скорее всего, имеет инкрементальный идентификатор - число, уникально идентифицирующую запись, который увеличивается на 1 (тот самый PRIMARY KEY AUTO_INCREMENT в MySQL). В этом случае его можно перевести в подобную строку, просто изменив ему систему исчисления, например, используя просто первые десять букв латинского алфавита вместо цифр (или даже увеличить количество цифр). Насколько понимаю, особого смысла у этого подхода нет (нет большой разницы, используется video/123123 или video/aBaB), и вряд ли он где-то применяется.