component /*https://www.bennadel.com/blog/2976-trying-to-generate-cryptographically-strong-random-tokens-in-coldfusion.htm*/ output = false hint = "Генерирует случайные токены с помощью Java SecureRandom." { /** * Инициализирует генератор токенов. * * @output false */ public any function init() { // Реализация генератора, используемая для построения случайных токенов. // SHA1PRNG не является алгоритмом по умолчанию во всех реализациях JVM, // поэтому он задается явно для предсказуемого поведения. generator = createObject( "java", "java.security.SecureRandom" ) .getInstance( javaCast( "string", "SHA1PRNG" ), javaCast( "string", "SUN" ) ) ; // После инициализации нужно сгенерировать случайный байт, // чтобы генератор сам засеялся через общий источник seed. // Это может блокироваться до накопления достаточной энтропии, // поэтому операция выполняется при инициализации, а не при первом использовании. generator.nextBytes( charsetDecode( " ", "utf-8" ) ); // Момент следующего пересева генератора. // Это снижает риск слишком долгой работы с одним и тем же seed. reseedAt = getNextReseedAt(); return( this ); } // Публичные методы. /** * Генерирует криптографически стойкий токен из заданного числа случайных байтов. * Байт-массив кодируется в форму, пригодную для использования в URL. * * @byteCount Количество случайных байтов для генерации токена. * @output false */ public string function nextToken( numeric byteCount = 32 ) { // Проверяем, нужен ли пересев генератора. if ( now() >= reseedAt ) { // Пересев выполняется под lock, чтобы уменьшить гонки между потоками. // Если lock не получен вовремя, поток продолжает работу с текущим состоянием генератора. lock name = "TokenGenerator.reseedCheck" type = "exclusive" timeout = 1 throwOnTimeout = false { // Повторная проверка внутри lock: другой поток мог уже обновить seed. if ( now() >= reseedAt ) { reseedAt = getNextReseedAt(); // Новый seed добавляется к уже существующему внутреннему состоянию генератора. generator.setSeed( generator.generateSeed( javaCast( "int", 32 ) ) ); } } } // Создаем буфер байтов, в который будут записаны случайные значения. var byteBuffer = charsetDecode( repeatString( " ", byteCount ), "utf-8" ); generator.nextBytes( byteBuffer ); return( encodeBytes( byteBuffer ) ); } // Приватные методы. /** * Кодирует массив байтов в строку токена. * * @bytes Кодируемый массив байтов. * @output false */ private string function encodeBytes( required binary bytes ) { var token = binaryEncode( bytes, "base64" ); // *** вот поэтому длина отличается от заявленной // мы хотим убрать все спецсимволы, потому что мы используем токен в качестве псевдослучайного суффикса token = replace( token, "+", "a", "all" ); token = replace( token, "/", "b", "all" ); token = replace( token, "=", "c", "all" ); return( token ); } /** * Вычисляет время следующего пересева генератора. * * @output false */ private date function getNextReseedAt() { return( dateAdd( "h", 1, now() ) ); } }