Помилка перевірки сеансу в Magento 1 EE v 1.14.3.x (і CE 1.9.3.x)


18

Я доглядаю за магазином Magento з 400-500 відвідувачами та 40-50 замовлень на день. Нещодавно систему було оновлено з Magento EE 1.14.2.4 до Magento EE 1.14.3.2, і я помітив деякі дивні винятки в журналах:

exception 'Mage_Core_Model_Session_Exception' in
/var/www/.../app/code/core/Mage/Core/Model/Session/Abstract/Varien.php:418

Я переслідував це виняток, і я знаю, що його звільняють, оскільки наступний код перевірки сеансу не підтверджує сеанс:

class Mage_Core_Model_Session_Abstract_Varien extends Varien_Object
{
// ...
    protected function _validate()
    {
//    ...
        if ($this->useValidateSessionExpire()
            && isset($sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP])
            && $sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP] < time() ) {

Цей if-блок був доданий у файл із останньою версією Magento. І це гальмівна зміна, мабуть, детальніше дивіться нижче.

Виняток трапляється досить часто, як десяток разів на день. але я не в змозі відтворити умови, які призводять до винятку, якщо я буквально не ставлю справжній стан вище. Винятки найчастіше трапляються на сторінках із детальною інформацією про продукт та на останньому кроці оформити одну сторінку. Магазин - це магазин b2b, користувач повинен увійти, щоб переглянути сторінку продукту або мати змогу оформити замовлення, означає, що користувач перенаправляється на сторінки для входу, коли сеанс недійсний / закінчився. На даний момент для мене важливіше вирішити цю проблему під час оформлення замовлення.

Що відбувається з точки зору користувача: Користувач заповнює кошик, приступає до оформлення замовлення і досягає останнього кроку, потім він натискає кнопку "подати замовлення", і нічого не відбувається. За лаштунками JS Magento виконує запит AJAX, і JS розраховує отримати JSON назад, але якщо трапиться ця помилка, повернеться HTML-код сторінки входу, який неможливо проаналізувати JavaScript, і він просто нічого не робить. Це дуже заплутано для користувачів.

Ну, це не повний сценарій для користувача, ми зв’язалися з користувачами, і вони сказали нам, що вони чекали кілька днів між заповненням кошика та поданням замовлення, що це саме означає важко зрозуміти, тому що люди просто цього не пам’ятають.

Тривалість сеансу PHP - 350000 (~ 4 дні в секундах) Тривалість файлу cookie - 345600 (4 дні)

Ось власне питання: як я можу дізнатися, яка поведінка користувачів призводить до винятку?

ОНОВЛЕННЯ Поки що я знаю, що виняток трапляється в наступних класах відповідно до зробленого запиту, для мене це, на жаль, нічого не означає.

/catalogsearch/result/?q=…    Mage_Core_Model_Session
/checkout/cart/               Mage_Core_Model_Session
/checkout/onepage/saveOrder/… Mage_Rss_Model_Session
/customer/account/loginPost/  Mage_Core_Model_Session
/customer/account/loginPost/  Mage_Reports_Model_Session
/customer/account/logout/     Mage_Reports_Model_Session
/catalog/product/view/…       Mage_Reports_Model_Session
/catalog/product/view/…       Mage_Tag_Model_Session

ОНОВЛЕННЯ 2 : сеанси зберігаються у файлах та очищаються сміттєзбірником PHP, незалежно від того, чи це вдалий вибір, чи не виходить за межі цього питання.


Пов’язано: maxchadwick.xyz/blog/…
Саймон

Відповіді:


24

Після деякої вдосконаленої налагодження, відстеження сеансу та роздумів про всю цю магію я зміг відтворити проблему та зрозуміти причину її. Я підготував невеличку ілюстрацію про терміни, її ви можете побачити нижче

час проблем

  • червоний прапор - це момент входу користувача та створення сеансу
  • блакитний прапор - це момент, коли користувач відкриває сторінку каталогу, припустимо, це сторінка категорії, яка відкривається.
  • зелений прапор - це момент, коли користувач подає замовлення ( /sales/order/save/...запит)

Ось як відтворити:

  1. Перш ніж почати: встановіть час очікування на сеанс PHP та час очікування файлів Magento 1440, що є значенням PHP за замовчуванням.
  2. Вбийте всі файли cookie або відкрийте вкладку анонімного перегляду.
  3. Перейдіть до магазину Magento та увійдіть у систему (див. Прапор 1)
  4. Перегляньте каталог і додайте деякі товари до кошика (Прапор 2)
  5. Пройдіть через касу і подайте замовлення. Зверніть увагу на час, коли ви це робили. (Прапор 3)
  6. Перегляньте каталог і додайте деякі товари до кошика (Прапор 4)
  7. Продовжуйте оновлювати сторінку кошика або переглядати сторінки каталогу так довго, щоб закінчився час, який ви налаштували на файли cookie magento (Прапори 5-6). Зауважте, що час між прапором 7 та прапором 3 повинен бути більшим за час очікування файлів cookie.
  8. Пройдіть через касу і подайте замовлення (Прапор 7). Подання замовлення не вдасться через виняток, описаний у моєму запитанні вище.

Причина:

Існують певні сеанси, які інстанціюються лише за заданими запитами, наприклад, Mage_Rss_Model_Sessionце лише інстанціювання під час фактичної реєстрації, а не під час перегляду каталогу. У той же час часова мітка сесії встановлюється лише тоді, коли сеанс був екземпляром. Це означає, що якщо між двома касами було достатньо часу, а сеанс тим часом не був убитий (оскільки користувач вийшов із системи або cookie закінчився), новий код Magento вважатиме цей сеанс не проходженням перевірки, і викине виняток, що звучить якось дивно я.

Як виправити:

Ну, у мене мало варіантів:

  1. Зачекайте, поки Magento відреагує на це і перегляне цей код.
  2. Тим часом видаліть цей код.
  3. Спробуйте встановити час очікування файлів Magento на 0, якщо це варіант для вас.

Як я це зрозумів:

  1. Я почав із додавання наступного до початкового коду Mage_Core_Model_Session_Abstract_Varien

    Mage::log(
        sprintf(
            'useValidateSessionExpire fail "%s" "%d" "%d" "%s" "%s" "%s"',
            print_r($sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP], 1),
            time(),
            $this->_time,
            get_class($this),
            session_name(),
            session_id()
        ),
        Zend_Log::DEBUG,
        'session-validation.log',
        true
    );
    

    це дало мені гарне уявлення про постраждалі класи та їх співвідношення та про те, скільки сеансів минуло. Але це не пояснювало, чому це відбувається і які дії користувача призводять до проблеми.

  2. Потім я задумався над тим, як я можу простежити всі зміни даних сеансу, і наткнувся на це запитання /superuser/368231/automatic-versioning-upon-file-change-modify-create-delete Я вирішив дати спроба gitі incronкомбінація, але після того, як я реалізував це і протестував у пісочниці, я зрозумів, що у мене дуже швидко вийде з місця на диску.

  3. Я вирішив створити невеликий скрипт PHP, який буде декодувати дані сеансу та писати журнали для кожного сеансу. Цей сценарій був названий користувачемincron

    <?php
    //log-session-data-change.php
    
    $sessionLogStoragePath = '/var/www/html/logged-session-storage/';
    
    $sessionFilePath = $argv[1];
    $sessionOperationType = $argv[2];
    $sessionFileName = basename($sessionFilePath);
    
    session_start();
    session_decode(file_get_contents($sessionFilePath));
    
    $logString = sprintf(
      '"%s","%s","%s",""' . PHP_EOL,
      date(DateTime::COOKIE),
      $sessionOperationType,
      $sessionFileName
    );
    
    if (file_exists($sessionFilePath)) {
      session_start();
      session_decode(file_get_contents($sessionFilePath));
    
      foreach ($_SESSION as $name => $data) {
        $value = '<empty>';
        if (isset($data['_session_validator_data']) && isset($data['_session_validator_data']['session_expire_timestamp'])) {
          $value = $data['_session_validator_data']['session_expire_timestamp'];
        }
        $logString .= sprintf(
          '"","","","%s","%s"' . PHP_EOL,
          $name,
          $value
        );
      }
    }
    
    file_put_contents($sessionLogStoragePath . $sessionFileName, $logString, FILE_APPEND);
    

    і ось відповідний incrontabзапис

    /var/www/html/magento-doc-root/var/session IN_MODIFY,IN_CREATE,IN_DELETE,IN_MOVE /usr/bin/php /var/www/html/log-session-data-change.php $@/$# $%

    вибірка вибірки

    "Wednesday, 05-Apr-2017 18:09:06 CEST","IN_MODIFY","sess_94rfglnua0phncmp98hbr3k524",""
    "","","","core","1491408665"
    "","","","customer_base","1491408665"
    "","","","catalog","1491408665"
    "","","","checkout","1491408665"
    "","","","reports","1491408494"
    "","","","store_default","1491408665"
    "","","","rss","1491408524"
    "","","","admin","1491408524"
    

PS:

Поточні версії обох

skin/frontend/enterprise/default/js/opcheckout.js 
src/skin/frontend/base/default/js/opcheckout.js

не в змозі обробити виняток вище під час запиту AJAX. Вони буквально нічого не показують користувачеві, а користувач фактично виходить з системи!

PPS:

Мабуть, також впливають версії Magento CE 1.9.3.x, див. https://github.com/OpenMage/magento-mirror/blame/magento-1.9/app/code/core/Mage/Core/Model/Session/Ab Abstract/ Varien.php

PPPS:

Коли я сказав "Видалити цей код тим часом". Я мав на увазі виключення наступного блоку

if ($this->useValidateSessionExpire()
    && isset($sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP])
    && $sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP] < time() ) {
    return false;
} else {
    $this->_data[self::VALIDATOR_KEY][self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP]
        = $validatorData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP];
}

це можна зробити так багато способів, включаючи:

  1. Просто видаліть цей біт з файлу
  2. Коментуючи це
  3. Повертаючись перед цим
  4. Здійснення $this->useValidateSessionExpire()повернення справжнім
  5. ...
  6. Це програмування - будьте креативними;)

Я просто відключив <Mage_Rss>і вирішив проблему (тимчасове виправлення) і подав квиток на підтримку magento.
Дамодар Баш'ял

1
@DamodarBashyal, будь ласка, майте на увазі, що питання стосується не лише каси. Це також впливає на сторінки продуктів, я вважаю, що можуть вплинути і деякі інші сторінки. Причина - для кожного дії контролера magento ініціалізується різний набір об'єктів сеансу. Я можу надати більше пояснень, якщо потрібно.
Антон Борицький

У мене виникли проблеми з API, під час створення відправлення я отримував помилку. Читання було нормально, але проблема була з записом, поки її не було відключено. Thx для інформації.
Дамодар Баш'ял

9

6. Це програмування - будьте креативними;)

Ще один спосіб виправити це (та покращити перевірку сеансу)

ColinM @ https://github.com/OpenMage/magento-lts

На даний момент код сеансу зберігає дані валідатора сеансу у кожному просторі імен, а також перевіряє їх щоразу, коли вводиться простір імен. Це погано, оскільки:

  1. Надзвичайно неефективний простір для зберігання сеансу. Дані валідатора часто містять понад 50% простору, який використовується простором імен, і коли є багато просторів імен, це складає до тони відходів. Зберігання сеансу можна різко скоротити за допомогою цього виправлення та при використанні пам’яті в пам'яті, наприклад, Redis або Memcached, що має велике значення.
  2. Неефективні цикли обчислень, оскільки декілька просторів імен означають кілька перевірок, і немає вагомих причин, щоб вони відрізнялися один від одного.
  3. Насправді створюються помилки, такі як # 394, де дані валідатора оновлюються на деяких запитах, але не на інших (тому вони можуть відрізнятися, але не повинні). Я не проходив тестування, але вважаю, що це також виправить це питання.
diff --git a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php
index 45d736543..ea6b464f1 100644
--- a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php
+++ b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php
@@ -35,6 +35,9 @@ class Mage_Core_Model_Session_Abstract_Varien extends Varien_Object
     const VALIDATOR_SESSION_EXPIRE_TIMESTAMP    = 'session_expire_timestamp';
     const SECURE_COOKIE_CHECK_KEY               = '_secure_cookie_check';

+    /** @var bool Flag true if session validator data has already been evaluated */
+    protected static $isValidated = FALSE;
+
     /**
      * Map of session enabled hosts
      * @example array('host.name' => true)
@@ -406,16 +409,21 @@ public function getValidateHttpUserAgentSkip()
     /**
      * Validate session
      *
-     * @param string $namespace
+     * @throws Mage_Core_Model_Session_Exception
      * @return Mage_Core_Model_Session_Abstract_Varien
      */
     public function validate()
     {
-        if (!isset($this->_data[self::VALIDATOR_KEY])) {
-            $this->_data[self::VALIDATOR_KEY] = $this->getValidatorData();
+        // Backwards compatibility with legacy sessions (validator data stored per-namespace)
+        if (isset($this->_data[self::VALIDATOR_KEY])) {
+            $_SESSION[self::VALIDATOR_KEY] = $this->_data[self::VALIDATOR_KEY];
+            unset($this->_data[self::VALIDATOR_KEY]);
+        }
+        if (!isset($_SESSION[self::VALIDATOR_KEY])) {
+            $_SESSION[self::VALIDATOR_KEY] = $this->getValidatorData();
         }
         else {
-            if (!$this->_validate()) {
+            if ( ! self::$isValidated && ! $this->_validate()) {
                 $this->getCookie()->delete(session_name());
                 // throw core session exception
                 throw new Mage_Core_Model_Session_Exception('');
@@ -432,8 +440,9 @@ public function validate()
      */
     protected function _validate()
     {
-        $sessionData = $this->_data[self::VALIDATOR_KEY];
+        $sessionData = $_SESSION[self::VALIDATOR_KEY];
         $validatorData = $this->getValidatorData();
+        self::$isValidated = TRUE; // Only validate once since the validator data is the same for every namespace

         if ($this->useValidateRemoteAddr()
                 && $sessionData[self::VALIDATOR_REMOTE_ADDR_KEY] != $validatorData[self::VALIDATOR_REMOTE_ADDR_KEY]) {
@@ -444,10 +453,8 @@ protected function _validate()
             return false;
         }

-        $sessionValidateHttpXForwardedForKey = $sessionData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY];
-        $validatorValidateHttpXForwardedForKey = $validatorData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY];
         if ($this->useValidateHttpXForwardedFor()
-            && $sessionValidateHttpXForwardedForKey != $validatorValidateHttpXForwardedForKey ) {
+                && $sessionData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY] != $validatorData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY]) {
             return false;
         }
         if ($this->useValidateHttpUserAgent()

Джерело: https://github.com/OpenMage/magento-lts/commit/de06e671c09b375605a956e100911396822e276a


Оновлення:

Виправлення для web/session/use_http_x_forwarded_for optionвідключеного параметра ... https://github.com/OpenMage/magento-lts/pull/457/commits/ec8128b4605e82406679c3cd81244ddf3878c379


1
це добре виглядає насправді, будь-який досвід використання цього у виробництві?
Антон Борицький

@AntonBoritskiy Так, я використовую це у виробництві. Працює ідеально.
sv3n

sv3n Чи є потенційні погані сторони цього способу рішення?
Вайшал Патель

@VaishalPatel, якщо є якісь потенційні погані сторони, я їх фактично не бачу :) Я використовую це на виробництві, і це вирішило всі проблеми валідації сеансу. Я б не публікував це, якщо у вас виникли проблеми, але якщо у вас є сумніви, будь ласка, запитайте тут: github.com/OpenMage/magento-lts/pull/406 . Можливо, деякі із "профі" ДП ще мають час переглянути це?
sv3n

Я поставлю на своє виробництво. У будь-якому випадку вона просувається до рішення.
Вайшал Патель

1

Як ви зберігаєте сеанси? (тобто у var / session / або в БД, або за допомогою інших двигунів кешування, таких як Redis або Memcached)

Незважаючи на те, що ви використовуєте, переконайтеся, що ваші дозволи на запис правильні var/session/(як правило, встановлено 755 для dirs та 644 для файлів) або якщо ви використовуєте Redis або Memcache, переконайтеся, що налаштування підключення та час очікування хороші для цих .

Inchoo має хороший підручник для Redis: http://inchoo.net/magento/using-redis-cache-backend-and-session-storage-in-magento/

Якщо ви використовуєте Memcache, перегляньте цю статтю (вона посилається на v1.10, але не повинна сильно відрізнятися): http://www.magestore.com/magento/magento-sesions-disappearing-with-memcache-turned-on.html

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

Нарешті, якщо ви використовуєте файлову систему для своїх сеансів, ви можете знайти полегшення, просто переключивши <session_save>вузол у local.xml"db" замість "файлів".

Від цього <session_save><![CDATA[files]]></session_save>

До цього <session_save><![CDATA[db]]></session_save>


дякую за підказку - я мав би додати інформацію до питання про те, як я фактично зберігаю сеанси, я зберігаю їх у файлах. Я щойно розібрався з оригінальним випуском, вважаю, що помилка Magento. Я
завершу

Чудово! ... Чи допомогла моя відповідь взагалі на вирішенні?
gtr1971

не дуже - дивіться мою відповідь
Антон Борицький

0

Деталі Антона Борицького є фантастичними. Але замість виключення цього блоку ви можете зробити локальну копію, щоб ви не редагували ядро ​​та не переписували блок на зразок:

if ($this->useValidateSessionExpire() ) {
    // If the VALIDATOR_SESSION_EXPIRE_TIMESTAMP key is not set, do it now
    if( !isset($sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP]) ) {
        // $this->_data is a reference to the $_SESSION variable so it will be automatically modified
        $this->_data[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP] = time() + $this->getCookie()->getLifetime();
        return true;
    } elseif ( $sessionData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP] < time() ) {
        return false;
    }
} else {
    $this->_data[self::VALIDATOR_KEY][self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP]
        = $validatorData[self::VALIDATOR_SESSION_EXPIRE_TIMESTAMP];
}

Це запевняє, що порівняння між time () та session_expire_timestamp виконується лише тоді, коли ключ існує, і що коли буде знайдено сеанс, у якому немає ключа (тобто попереднього сеансу 1.9.3), додано ключ.


Додавання локальної копії та перевизначення, звичайно, краще, ніж зміна основних файлів, ми внутрішньо підтримуємо список патчів, які автоматично застосовуються під час складання проекту, тому що Magento випускав пару помилок, як це нещодавно.
Антон Борицький

У той же час я не бачу, як ваша зміна виправляє оригінальну проблему, могла б додати трохи більш розширене пояснення?
Антон Борицький

Анто Борицький, це гарний крик зі списком.
Вайшал Патель

Анто Борицький, Новий ключ використовується для перевірки дійсності часової мітки сесії. $ sessionData походить від $ this -> _ data [self: VALIDATOR_KEY]; але ключ session_expire_timestamp до сеансу додається лише $ this-> getValidatorData (); функція та зберігається у $ this -> _ data [...] в кінці виклику функції. Таким чином, проблема полягає в тому, що в існуючих сеансах цей ключ session_expire_timestamp недоступний.
Вайшал Патель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.