Исправляем постоянное обновление PHPSESSID в битриксе на поддомене “www”

Предыстория

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

  • При попытке что-либо сохранить, изменить или удалить в админке сайта ничего не получалось, страница в лучшем случае просто перезагружалась, в худшем случае вылезали разного рода ошибки о том, что невозможно сохранить изменения.
  • При просмотре заказа в админке битрикса выводилось сообщение об ошибке “Ошибка обработки запроса. Наиболее вероятные причины: у пользователя недостаточно прав; проблемы с сохранением сессий PHP; часть данных POST-запроса обрезается PHP либо веб-сервером.”
  • Стали вылезать проблемы в публичной части сайта на AJAX элементах. Например, перестала обновляться сумма корзины при изменении количества по любой из позиций. И иногда не подгружались блоки на страницах.

Причем было так же замечено, что в режиме приватного просмотра (он же режим инкогнито) все работало нормально и никаких проблем не возникало. Позже опытным путем было выявлено, что если почитстить кэш и куки тоже помогает, но не на долго, через какое-то время причина повторяется. Чуть позже выявли, что достаточно удалить тольок куки.

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

Причины возникновения проблемы

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

Оказалось что все дело в том, что в куках браузера сохраняется индетификатор PHP сессии и передается он в заголовках браузера каждый раз при обновлении страницы, или AJAX запросе. Однако дальше обнаружил, что в данных PHPSESSID в куках для домена оказлось несколько, т.к. сайт работает на домене www.site.ru. Вот эта вот приписка поддомена WWW в домене сайта и создает весь этот бардак. О том почему именно WWW дает такой эффект я отвечу чуть ниже в посте, а сейча расскажу что вообще происходит с ID сессии. Так как при первом визите на сайт в стартует создание PHP сессии session_start() и сохраняает в $_COOKIE[“PHPSESSID”]. Дальше все время пока активна сессия в $_COOKIE[“PHPSESSID”] и session_id() данные должны совпадать. В этом случае все работает в штатном режиме. Однако в какой-то момент при выходе из админки сайта в переменной $_COOKIE[“PHPSESSID”] вдруг появляется другой идентификатор, который не соответсвует требованиям битрикса и поэтому каждый раз при загрузке страницы обновляется идентификатор сессии, но по каким-то причинам он не сохраняется в куках. В $_COOKIE[“PHPSESSID”] зависает “битый” ID.

При детальном изучении стало понятно почему это происходит. В консоле браузера видно, что для домена www.site.ru есть две куки PHPSESSID: одна для хоста .www.site.ru, а вторая для хоста .site.ru. Так вот эта вторая кука и содержит битый ID и появляется она в какой-то момент при выходе из админки по кнопке “Выйти”.

1 вариант решения проблемы (не рабочий!)

Для решения данной проблемы нужно в файле dbconn.php добавить строку заменив в ней site.ru на ваш домен.

setcookie('PHPSESSID', '', time() - 100, '/', .site.ru);

Руководствуясь этой логикой попытался добавить проверку на расхождение PHPSESSID в куках и session_id().

if($_COOKIE["PHPSESSID"] != session_id())
    setcookie('PHPSESSID', '', time() - 100, '/', .site.ru);

Казалось бы вот оно счастье, но не тут-то было! Данный способ не сможет работать, т.к. во время обработки файла dbconn.php еще не инициирована PHP сессия, поэтому session_id() будет всегда пустым. А просто безжалостно убивать PHPSESSID нет смысла.
Исходя из всего этого переходим к второму варианту решения проблемы.

2 вариант решения проблемы (как показал опыт, работает не на 100% у всех)

В битриксе PHP сессия стартует в файле /bitrx/modules/main/include.php.

На снимке экрана выше видно, что следом за инициацией PHP сессии отрабатывает вызов функций подписки на событие OnPageStart.

Помня о том, что желательно код ядра не модифицировать, добавляем в файле init.php свою функцию обработчик событие OnPageStart.

RegisterModuleDependences("main", "OnPageStart", "main", "MyCookieSession", "FixDoubleSessionId", "100");

class MyCookieSession
{
  public static function FixDoubleSessionId()
  {
    
    if(
        session_id()
        && isset($_COOKIE["PHPSESSID"])
        && $_COOKIE["PHPSESSID"] != session_id()
    )
    {
        setcookie('PHPSESSID', '', time() - 100, '/');
        header('Location: '.$_SERVER['REQUEST_URI]']);
    }
  }
}

Важный момент! После того, как мы понимаем, что ID сессии в куках и session_id() различны, нам нужно отправить браузеру заголовки о сбросе PHPSESSID в куках и второй заголовок, который будет перезагружать страницу. Если не перезагрузить страницу, то в браузер запишется устаревшее значение PHPSESSID, и тогда при следующей загрузке старницы опять будут расхождения.

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

3 вариант решения (на тестировании)

С течением времени выяснилось, что предыдущий способ работает, но не у всех, т.к. продолжали приходить жалобы, что у кого-то отображается “белый экран” вместо содержимого, которое должно было подгружаться через AJAX запрос. Конечно локальная очистка куков браузера помогала на 100% и больше проблем не возникало, но хочется же “сделать чудо”, чтобы никто из пользователей не обременялся чисткой куков, а все само волшебным образом заработало.

Шаг 1. Помощь техподдержки Битрикса

От того, что мысли на тему решения данной головоломки в голове закончились, решил привлечь техподдержку Битрикса на помощь. В итоге получил два совета: 1 – переместить хранение ID сессий в БД, и 2 – сделать настройку сайта по инструкции https://dev.1c-bitrix.ru/learning/course/?COURSE_ID=103&LESSON_ID=20670&LESSON_PATH=8799.3987.20670.

Для смены хранилища сессий в файле /bitrix/.settings.php добавляем следующий фрагмент кода:

'session' =>
array (
  'value' =>
  array (
    'mode' => 'default',
   'handlers' =>
   array (
     'general' =>
     array (
       '_fromSecurity' => true,
       'type' => 'database',
     ),
   ),
 ),
 'readonly' => true,
),

После тестов подолжительностью около 1,5 недели выявилось, что проделанной работы все еще не достаточно, и нужно придумать что-то еще.

Шаг 2. Переделка функции очистки куков с редиректом в init.php

Отрефликсировав предыдущие опыты и эксперименты я решил пойти по пути жесткого перебора значений хостов для хранений куки PHPSESSID. Для этго сделал вместо циклического пошаговый сброс куки.

RegisterModuleDependences("main", "OnPageStart", "main", "MyCookieSession", "FixDoubleSessionId", "100");

class MyCookieSession
{
  public static function FixDoubleSessionId()
  {
    
    if(
        env('CLEAR_COOCKIE_USE') 
        && session_id()
        && isset($_COOKIE["PHPSESSID"])
        && $_COOKIE["PHPSESSID"] != session_id()
    )
    {
        
        $needReload = false;
        $curStep = 0;
        $domain = "";

        if(!isset($_COOKIE["SESS_BUG_STEP"]) || $_COOKIE["SESS_BUG_STEP"] == ""){
          $domain = '.youdomain.ru';
          $needReload = true;
          $curStep = 1;
        }
        elseif($_COOKIE["SESS_BUG_STEP"] == "1"){
          $domain = 'youdomain.ru';
          $needReload = true;
          $curStep = 2;
        }
        elseif($_COOKIE["SESS_BUG_STEP"] == "2"){
          $domain = '.www.youdomain.ru';
          $needReload = true;
          $curStep = 3;
        }
        elseif($_COOKIE["SESS_BUG_STEP"] == "3"){
          $domain = 'www.youdomain.ru';
          $needReload = true;
          $curStep = 4;
        }
        elseif($_COOKIE["SESS_BUG_STEP"] >= "4"){
          $domain = '';
          $needReload = true;
          $curStep++;
        }

        if($needReload){
          setcookie('SESS_BUG_STEP', $curStep, time() + 100, '/');
          setcookie('PHPSESSID', '', time() - 100, '/', $domain);

          header('Location: '.$_SERVER['REQUEST_URI]']);
        }


    }
  }
}

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

Таким образом опытным путем пришел к следующему приоритету:

  • .youdomain.ru“,
  • youdomain.ru“,
  • .www.youdomain.ru“,
  • www.youdomain.ru“.

Итоги

Данный вариант помог мне решить проблему слетающих PHPSESSID.

Повторюсь, этот способ актуален для сайтов, которые работают на домене с WWW в качестве основного домена.

Модуль исправления PHPSESSID для Битрикса

В виду популярности вопроса я решил создать модуль для Битрикса, который помогает исправить дубли PHPSESSID. Пользуйтесь на здоровье! Буду рад видеть ваши отзывы и комментарии ))

Бесплатно скачать и установить модуль FIX PHPSESSID из маркетплейса Битрикса

Источники

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Посты по теме

CRON для сайта

Как сделать свой сайт быстрее и эффективнее путем настройки фоновых заданий на CRON

Настройка CRON для VtigerCRM

Для того, чтобы правильно работали фоновые задания в CRM системе обязаельно нужно настроить запуск исполняющего файла по CRON. Для этого в VtigerCRM есть специальные файлы для Linux и Windows серверов Все что нужно сделать, это добваить в планировщик CRON на линксовом сервере выполнение задачи ~/cron/vtigercron.sh с интервалом 1 раз в минуту.

Исправление PHP Parse error: Unclosed '{' on line в Битриксе

Иногда в Битриксе может возникать ошибка “PHP Parse error: Unclosed ‘{‘ on line …”. Обычно такую ошибку можно обнаружить после переезда на новый хостинг. Для испраления этой ошибки необходимо установить значение on параметра short_open_tag в настройках PHP. Делается это в панели управления хостингом.

Что делать если Bitrix MySQL использует все ресурсы процессора

Однажды столкнулся с интересной проблемой, MySQL стал пожирать полностью все ресурсы процессора. Причем “захват” ресурсов происходил плавно с выраженными скачками на графике мониторинга Провалы на графике это ручной перезапуск службы MySQL Причина утечки процессорных ресурсов под MySQL В поисках причины были исследованы длинные запросы к БД и перепробваны различные комбинации настроек MySQL, но ничего не […]

Как изменить язык сайта Битрикс для раздела или страницы

Легкий способ локально изменить ящык сайта Bitrix для отдельных страниц или разделов без применения механизма многосайтовости.