Повышаем стабильность сессии в CakePHP 2.x

в 9:53, , рубрики: CakePHP, php, Блог компании Web-payment.ru, сессии

От переводчика: при разработке Web-payment.ru на фреймворке CakePHP мы сталкивались с самого начала с тем, что логаут пользователей происходил каждые несколько часов, а это слишком короткий промежуток времени. При этом сколь большие значения timeout и cookieTimeout мы бы не выставляли в настройках ядра, ничего не менялось. Данная статья решила для нас эту проблему.

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

В документации Cake также не говорится, что для длительной работы php-сессий необходимо увеличить значение внутренней переменной max_lifetime. Я вообще наткнулся на нее случайно, потому что всегда думал, что об этом позаботился сам фреймворк, однако даже после того, как я сделал базу данных контейнером сессий, я не заметил каких-либо улучшений. Поэтому я подумал, что чистка памяти на стороне сервера здесь, видимо, вообще не причем, ну или по крайней мере, влияет не только она одна.

Итак, в итоге мы имеем следующее:

  • Выставление таймаутов было пустой тратой времени, также, как и изменение Security level, который я выставил на low.
  • Переезд с php/cake на БД тоже не сработал, хотя он, конечно, предоставляет немного больше контроля.
  • Увеличение значения gc_maxlifetime в php.ini опять же, не дало особого эффекта.

После всех этих действий, логауты продолжались через совершенно разные промежутки времени — иногда через несколько минут, иногда — несколько часов, но не более того. Эта проблема имеет важное значение не только для создания социальных сетей, но и для других сайтов, работающих на Cake.

Я несколько раз пытался добавить зашифрованные cookie, такие, как RememberMe для быстрого повторного логина в тот момент логаута, однако из-за бага PHP, связанного с srand/mt_srand и suhosin это решение не работало. Благодаря посту Miles’a о баге, я решил взяться за проблему всерьез и что-нибудь с этим поделать (многие пользователи жаловались на необъяснимые логауты за последние несколько месяцев).

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

Автоматический возврат логина как вариант решения проблемы

Прошло несколько лет, и вот, наконец, я все-таки воспользовался черновыми работами Miles’a и настроил его компонент AutoLogin для работы с моими приложениями.
Суть работы компонента заключается в том, что он сохраняет данные пользователя в cookie во время логина, а как только сессия по неизвестной причине теряется снова, компонент восстанавливает ее. Это происходит тихо и никак не мешает работе пользователя.
Код компонента вы можете найти в в моем плагине Tools (пользуйтесь именно этим репозиторием).

Особенности использования

Успешно проверив его в нескольких Cake2.x приложениях, я хочу поделиться с вами простым руководством по его использованию. Для его подключения вам нужно просто добавить компонент глобально в AppController:

public $components = array('Session', 'RequestHandler', 'AutoLogin', 'Auth', ...);

Важно объявить его подключение до подключения компонента авторизации, чтобы избежать возникновения любых ошибочных сообщений об “отсутствии авторизации”.

Вы можете использовать /Config/configs.php (или любой другую директорию конфигурации) для определения его настроек:

$config['AutoLogin'] = array(
    'controller' => 'account',
    'username' => 'login',
    'requirePrompt' => false
    ... //для ознакомления с другими параметрами смотрите описание компонента
);

Я прошу вас соблюдать осторожность при использовании последнего параметра — requirePrompt. При его отключении, AutoLogin будет использоваться для всех попыток залогиниться. Это требует от пользователей постоянно делать правильный выход из аккаунтов во время использования сайта в общественных сетях (особенно опасно для интернет-кафе, где посторонние люди могут завладеть аккаунтом пользователя даже спустя несколько дней после последнего использования). Поэтому вы должны убедиться, что компонент используется только на сайтах, где все пользователи предупреждены и знают об этом.

То есть в обычном случае вам нужно добавить чекбокс в форму авторизации:

if (Configure::read('AutoLogin.active')) {
    echo $this->Form->input('auto_login', array('type'=>'checkbox', 'label'=>__('Запомнить меня')));
}

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

Приведенную выше конструкцию if можно опустить. Я использую ее для динамического включения/выключения компонента в зависимости от среды, в которой он работает, однако если вы собираетесь ее использовать, убедитесь, что вы присвоили active значение true в Configure.

Теперь об исправлении бага suhosin. Согласно объяснению Miles по ссылкам выше, большинство линуксовых апач-сред по умолчанию поставляются с патчем suhosin, который изменяет принцип работы srand. В случае с моим WAMP-сервером это не так, поэтому на нем все работает сразу, однако для того, чтобы компонент заработал в линукс-среде, вам нужно всего лишь добавить в конец /etc/php5/apache2/php.ini вот эту строчку:

suhosin.srand.ignore = Off

И не забудьте перезагрузить апач или сделать force-reload для /etc/init.d/apache2.
Совет: Я добавил пример для проверки наличия этого бага (смотрите файл для проверки в репозитории). Если вы не уверены, если ли у вас эта проблема, запустите его в своей среде.

Другие советы: Если вы хотите отключить AutoLogin для конкретного сайта, вам достаточно просто изменить значение active на false в конфиге сайта или сделать это динамически в конструкторе контроллеров. Всегда помните: вы не сможете добавлять его в список компонентов динамически, поскольку вам нужно включать его перед Auth-частью, однако вы всегда сможете отключить компонент, если тот уже был добавлен туда заранее.

Дебаг-режимом по умолчанию является auto-detect. В процессе разработки (debug > 0) он включен изначально, однако вы, конечно же, можете перезаписать это значение в своих конфигах.
Другим важным параметром является expires — по умолчанию оно равно двум неделям и легко поддается изменению.

Альтернативы

В 2.x теперь также есть альтернативные варианты решения проблемы. Один из них — использовать специальный адаптер CookieAuthenticate.

Разбор механизма работы с сессиями в Cake

Мне потребовалось немало времени, чтобы детально разобраться в принципе работы сессий. Теперь я наконец-то понял. Я хочу обратить особое внимание на то, что использование приведенного выше кода без глубокого его понимания скорее всего просто замаскирует проблему… ну или вылечит только симптомы болезни, если хотите. Да, этот способ будет работать, однако я думаю, что вам больше понравилось бы, если бы все работало и без применения описанных выше трюков с запоминаниями сессии.

Для начала нам надо понять, как сессии создаются, проходят валидацию и обновляются при переходе по клику:

  • Пользователь приходит в первый раз, сессионные cookie генерируются (и действуют x секунд), значение таймаута сессии сохраняется в них (y секунд)
  • Юзер снова кликает: Конечно же мы увеличиваем срок действия сессии (определенные y секунд + еще z секунд), сохраняя его в cookie в виде значения
  • Однако cookie, сами по себе, не получают новый срок жизни (все те же x секунд). Это ограничение в обработке cookie, время их таймаута фиксировано.

Выяснив это, становиться совершенно ясно, что когда срок действия cookie, заданный при ее создании истечет, сессия будет уничтожена, даже если вы увеличеки срок ее жизни до нескольких лет. Таким образом, крайне важно понимать, что для того, чтобы эти настройки имели смысл, вам нужно задать большое значение для длительности сессии и еще большее — для времени жизни cookie:

Configure::write('Session', array(
    'defaults' => 'php',
    'timeout'  => 14400,  // 4 часа, обращается к 'session.gc_maxlifetime' в //настройках PHP
    'cookieTimeout' => 10 * 14400, // 20 часов, обращается к 'session.cookie_lifetime' в //настройках PHP 
    // ...
));

Оба значения выставлены в минутах в конфиге CakePHP.
С учетом этих изменений, юзер выходит из сессии, если он:

  • не произвел никакой сессионной активности (клики, ajax) в течении 4 часов
  • достиг 20-ти часового cookie таймаута (независимо от активности)

А поскольку такие настройки будут устраивать ему логаут каждые 20 часов, имеет смысл сделать значения побольше (несколько месяцев, например) или использовать его в сочетании с описанным выше компонентом для запоминания сессии.

Итак, повторим снова:

'session.gc_maxlifetime' => относительная величина, увеличивается при активности
'session.cookie_lifetime' => абсолютное значение, может быть присвоено только в //начале сессии

И не забудьте проверить свои ini-параметры, которые относятся к session.gc_divisor и andsession.gc_probability чтобы убедиться, что очистка памяти работает и проводиться в нужное время.

Автор: cigulev

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js