Sitzungsvalidierungsfehler in Magento 1 EE v 1.14.3.x (und CE 1.9.3.x)


18

Ich betreue einen Magento-Shop mit 400-500 Besuchern und 40-50 Bestellungen pro Tag. Kürzlich wurde das System von Magento EE 1.14.2.4 auf Magento EE 1.14.3.2 aktualisiert und ich habe einige merkwürdige Ausnahmen in den Protokollen festgestellt:

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

Ich habe diese Ausnahme verfolgt und weiß, dass sie ausgelöst wird, weil der folgende Sitzungsvalidierungscode die Sitzung nicht validieren kann:

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() ) {

Dieser if-Block wurde der Datei mit der neuesten Version von Magento hinzugefügt. Und dies ist anscheinend eine Bremsänderung, siehe weitere Details unten.

Die Ausnahme passiert ziemlich oft, wie etwa ein Dutzend Mal pro Tag. Aber ich bin nicht in der Lage, Bedingungen, die zur Ausnahme führen, neu zu erstellen, es sei denn, ich habe die obige Bedingung wörtlich erfüllt. Die Ausnahmen treten am häufigsten auf Produktdetailseiten und im letzten Schritt einer Seitenprüfung auf. Der Shop ist ein b2b-Shop. Der Benutzer muss eingeloggt sein, um die Produktseite zu sehen oder zur Kasse zu gehen. Dies bedeutet, dass der Benutzer zur Anmeldeseite umgeleitet wird, wenn die Sitzung ungültig ist / abgelaufen ist. Momentan ist es mir wichtiger, dieses Problem beim Checkout zu beheben.

Was passiert aus Benutzersicht: Der Benutzer füllt den Einkaufswagen, geht zur Kasse und erreicht den letzten Schritt. Dann drückt er den "Bestellung abschicken" -Button und es passiert nichts. Hinter den Kulissen führt Magentos JS eine AJAX-Anfrage aus und JS erwartet, JSON zurück zu erhalten. Wenn dieser Fehler auftritt, wird der HTML-Code der Anmeldeseite zurückgegeben, der nicht von JavaScript analysiert werden kann, und es geschieht einfach nichts. Das ist super verwirrend für User.

Nun, das ist kein vollständiges Benutzerszenario, wir haben uns mit den Benutzern in Verbindung gesetzt und sie haben uns gesagt, dass sie einige Tage zwischen dem Füllen des Einkaufswagens und dem Absenden der Bestellung gewartet haben. Was das genau bedeutet, ist schwer herauszufinden, weil sich die Leute einfach nicht daran erinnern.

Lebensdauer der PHP-Sitzung - 350000 (~ 4 Tage in Sekunden) Lebensdauer der Cookies - 345600 (4 Tage)

Hier ist die eigentliche Frage: Wie kann ich herausfinden, welche Art von Benutzerverhalten zur Ausnahme geführt hat?

UPDATE Soweit ich weiß, dass in folgenden Klassen Ausnahmen gemäß der gestellten Anfrage auftreten, bedeutet das für mich leider nichts.

/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

UPDATE 2 : Sitzungen werden in Dateien gespeichert und vom Garbage Collector für PHP-Sitzungen bereinigt. Ob dies eine gute Wahl ist oder nicht, ist nicht Gegenstand dieser Frage.


Antworten:


24

Nach einigem fortgeschrittenen Debuggen, Nachverfolgen von Sitzungen und Nachdenken über all diese Magie war ich in der Lage, das Problem zu reproduzieren und den Grund dafür zu verstehen. Ich habe eine kleine Timing-Illustration vorbereitet, die Sie unten sehen können.

problem zeit

  • Rote Flagge ist der Moment der Benutzeranmeldung und der Sitzungserstellung
  • Die blaue Flagge ist der Moment, in dem der Benutzer die Katalogseite öffnet. Nehmen wir an, es handelt sich um eine Kategorieseite, die geöffnet wird.
  • Grüne Flagge ist der Moment, in dem der Benutzer die Bestellung aufgibt ( /sales/order/save/...Anfrage)

So reproduzieren Sie:

  1. Bevor Sie beginnen: Setzen Sie Ihr PHP-Sitzungszeitlimit und das Magento-Cookie-Zeitlimit auf 1440, was ein Standard-PHP-Wert ist.
  2. Töte alle deine Cookies oder öffne den Inkognito-Tab.
  3. Gehe zu deinem Magento-Shop und logge dich ein (siehe Flag 1)
  4. Katalog durchgehen und einige Produkte in den Warenkorb legen (Flagge 2)
  5. Gehen Sie die Kasse durch und geben Sie eine Bestellung auf. Notieren Sie sich die Zeit, als Sie es getan haben. (Flagge 3)
  6. Katalog durchgehen und einige Produkte in den Warenkorb legen (Flagge 4)
  7. Aktualisieren Sie Ihre Warenkorbseite oder durchsuchen Sie die Katalogseiten so lange, bis das von Ihnen für Magento-Cookies konfigurierte Zeitlimit abgelaufen ist (Flags 5-6). Beachten Sie, dass die Zeit zwischen Flag 7 und Flag 3 länger als das Cookie-Timeout sein sollte.
  8. Gehen Sie zur Kasse und geben Sie eine Bestellung ab (Flag 7). Die Übermittlung der Bestellung schlägt aufgrund der in meiner Frage oben beschriebenen Ausnahme fehl.

Grund:

Es gibt bestimmte Sitzungen, die nur bei bestimmten Anforderungen instanziiert werden, z. B. Mage_Rss_Model_Sessionnur beim eigentlichen Checkout und nicht beim Durchsuchen des Katalogs. Gleichzeitig wird der Ablaufzeitstempel der Sitzung nur festgelegt, wenn die Sitzung instanziiert wurde. Das bedeutet, dass der neue Magento-Code eine Ausnahme auslöst, wenn zwischen zwei Auscheckvorgängen genügend Zeit liegt und die Sitzung zwischenzeitlich nicht beendet wurde (da der Benutzer abgemeldet ist oder das Cookie abgelaufen ist) mich.

Wie repariert man:

Nun, ich habe einige Möglichkeiten:

  1. Warten Sie, bis Magento darauf reagiert und den Code erneut überprüft.
  2. Entfernen Sie diesen Code währenddessen.
  3. Versuchen Sie, das Zeitlimit für Magento-Cookies auf 0 zu setzen, wenn dies für Sie eine Option ist.

Wie habe ich das herausgefunden:

  1. Ich habe mit dem Hinzufügen des Folgenden zum Originalcode von begonnen 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
    );

    Es gab mir einen guten Einblick in die betroffenen Klassen und ihre Korrelation und wie viel Sitzung abgelaufen waren. Das erklärte aber nicht, warum es passiert und welche Benutzeraktionen zum Problem führen.

  2. Dann begann ich zu überlegen, wie ich alle Änderungen an den Sitzungsdaten nachverfolgen kann. Dabei stellte sich die Frage /superuser/368231/automatic-versioning-upon-file-change-modify-create-delete, die ich angegeben hatte Ein Versuch gitund eine incronKombination, aber nachdem ich sie implementiert und in einer Sandbox getestet hatte, wurde mir klar, dass mir in der Produktion sehr schnell der Speicherplatz ausgehen wird.

  3. Ich habe beschlossen, ein kleines PHP-Skript zu erstellen, das Sitzungsdaten dekodiert und Protokolle für jede Sitzung schreibt. Dieses Skript wurde von aufgerufenincron

    <?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);

    und hier ist der entsprechende incrontabEintrag

    /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 $@/$# $%

    Beispielausgabe

    "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:

Aktuelle Versionen von beiden

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

sind nicht in der Lage, die oben genannte Ausnahme während der AJAX-Anforderung zu behandeln. Sie zeigen dem Benutzer buchstäblich nichts an, während der Benutzer effektiv abgemeldet wird!

PPS:

anscheinend sind auch Magento CE 1.9.3.x-Versionen betroffen, siehe https://github.com/OpenMage/magento-mirror/blame/magento-1.9/app/code/core/Mage/Core/Model/Session/Abstract/ Varien.php

PPPS:

Als ich sagte "Entferne diesen Code in der Zwischenzeit." Ich wollte den folgenden Block ausschließen

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];
}

Sie können das auf so viele Arten tun, einschließlich:

  1. Löschen Sie einfach dieses Bit aus der Datei
  2. Kommentiere es aus
  3. Davor zurückkehren
  4. Herstellung $this->useValidateSessionExpire() return true
  5. ...
  6. Es programmiert - sei kreativ;)

Ich habe gerade deaktiviert <Mage_Rss>und das Problem behoben (temporäre Korrektur) und das Ticket beim Magento-Support hinterlegt.
Damodar Bashyal

1
@ DamodarBashyal Bitte beachten Sie, dass das Problem nicht nur die Kasse betrifft. Es wirkt sich auch auf Produktseiten aus. Ich glaube, dass auch einige andere Seiten betroffen sein könnten. Grund - Bei jeder Magento-Controller-Aktion wird ein anderer Satz von Sitzungsobjekten initialisiert. Bei Bedarf kann ich weitere Erklärungen abgeben.
Anton Boritskiy

Ich hatte ein Problem mit der API. Beim Erstellen der Sendung wurde ein Fehler gemeldet. Das Lesen war in Ordnung, aber es gab ein Problem mit dem Schreiben, bis es deaktiviert wurde. Danke für die Info.
Damodar Bashyal

9

6. Es programmiert - sei kreativ;)

Eine andere Möglichkeit, dies zu beheben (und die Sitzungsvalidierung zu verbessern)

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

Der Sitzungscode speichert derzeit die Sitzungsüberprüfungsdaten in jedem Namespace und überprüft sie auch jedes Mal, wenn der Namespace aufgerufen wird. Das ist schlecht, weil:

  1. Extrem ineffizienter Sitzungsspeicher. Die Validator-Daten machen häufig mehr als 50% des von einem Namespace belegten Speicherplatzes aus. Wenn viele Namespaces vorhanden sind, bedeutet dies eine Menge Abfall. Der Sitzungsspeicher kann mit diesem Patch drastisch reduziert werden, und wenn ein In-Memory-Speicher wie Redis oder Memcached verwendet wird, ist das sehr wichtig.
  2. Ineffiziente Berechnungszyklen, da mehrere Namespaces mehrere Überprüfungen bedeuten und es keinen guten Grund dafür gibt, sich voneinander zu unterscheiden.
  3. Tatsächlich entstehen Fehler wie # 394, bei denen die Validierungsdaten bei einigen Anforderungen aktualisiert werden, bei anderen jedoch nicht (sie können also abweichen, sollten es aber nicht sein). Ich habe nicht getestet, aber ich glaube, dass dies auch das Problem beheben wird.
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()

Quelle: https://github.com/OpenMage/magento-lts/commit/de06e671c09b375605a956e100911396822e276a


Aktualisieren:

Korrektur für web/session/use_http_x_forwarded_for optiondeaktivierte Option ... https://github.com/OpenMage/magento-lts/pull/457/commits/ec8128b4605e82406679c3cd81244ddf3878c379


1
das sieht eigentlich gut aus, wie sieht es mit der produktion aus?
Anton Boritskiy

@AntonBoritskiy Ja, ich benutze dies in der Produktion. Funktioniert perfekt.
SV3N

sv3n gibt es mögliche schlechte seiten dieser lösungsmethode?
Vaishal Patel

@ VaishalPatel Wenn es irgendwelche potenziellen schlechten Seiten gibt, sehe ich sie nicht wirklich :) Ich benutze dies für die Produktion und es löste alle Probleme bei der Sitzungsvalidierung. Ich würde dies nicht posten, wenn ich irgendwelche Bedenken hätte, aber wenn Sie Zweifel haben, fragen Sie bitte hier: github.com/OpenMage/magento-lts/pull/406 . Vielleicht haben einige der SE "Profis" auch etwas Zeit, dies zu überprüfen?
Sv3n

Ich werde auf meine Produktion setzen. Auf jeden Fall geht es auf dem Weg zu einer Lösung.
Vaishal Patel

1

Wie speichern Sie Sitzungen? (dh in var / session / oder in der DB oder mit anderen Caching-Engines wie Redis oder Memcached)

Stellen Sie sicher, dass Ihre Schreibberechtigungen für die von Ihnen verwendete Version korrekt sind var/session/ (normalerweise 755 für Verzeichnisse und 644 für Dateien), oder stellen Sie bei Verwendung von Redis oder Memcache sicher, dass die Einstellungen für Verbindung und Zeitüberschreitung für diese Einstellungen geeignet sind .

Inchoo hat ein gutes Tutorial für Redis: http://inchoo.net/magento/using-redis-cache-backend-and-session-storage-in-magento/

Wenn Sie Memcache verwenden, lesen Sie diesen Artikel (er bezieht sich auf Version 1.10, sollte sich aber nicht wesentlich unterscheiden): http://www.magestore.com/magento/magento-sessions-disappearing-with-memcache-turned-on.html

Wenn Sie zufällig etwas wie Lack verwenden, gab es in der Vergangenheit Probleme mit Sitzungen, bei denen bestimmte Seiten gelocht werden mussten.

Wenn Sie das Dateisystem für Ihre Sitzungen verwenden, können Sie Abhilfe schaffen, indem Sie einfach den <session_save>Knoten in Ihrem wechselnlocal.xml auf "db" anstelle von "files" setzen.

Davon <session_save><![CDATA[files]]></session_save>

Dazu <session_save><![CDATA[db]]></session_save>


danke für den hinweis - ich hätte die info zu der frage hinzufügen sollen, wie ich sitzungen tatsächlich speichere, ich speichere sie in dateien. Ich habe gerade die ursprüngliche Ausgabe herausgefunden, ich halte das für einen Magento-Fehler. Ich werde es zusammenfassen und in Kürze eine Antwort veröffentlichen
Anton Boritskiy

Super! ... Hat meine Antwort bei der Lösung überhaupt geholfen?
gtr1971

nicht wirklich - siehe meine Antwort
Anton Boritskiy

0

Das Detail von Anton Boritskiy ist fantastisch. Aber anstatt diesen Block auszuschließen, können Sie eine lokale Kopie erstellen, damit Sie den Kern nicht bearbeiten und den Block wie folgt umschreiben:

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];
}

Dies stellt sicher, dass der Vergleich zwischen time () und session_expire_timestamp nur ausgeführt wird, wenn der Schlüssel vorhanden ist, und dass der Schlüssel hinzugefügt wird, wenn eine Sitzung gefunden wird, die keinen Schlüssel hat (dh eine Sitzung vor 1.9.3).


Das Hinzufügen einer lokalen Kopie und das Überschreiben ist natürlich viel besser als das Ändern von Kerndateien. Wir pflegen intern eine Liste von Patches, die automatisch während der Projekterstellung angewendet werden, da Magento in letzter Zeit einige derartige Fehler behoben hat.
Anton Boritskiy

Gleichzeitig sehe ich nicht, wie Ihre Änderung das ursprüngliche Problem behebt, könnte eine etwas ausführlichere Erklärung hinzufügen?
Anton Boritskiy

Anto Boritskiy, das ist ein guter Ruf mit der Liste.
Vaishal Patel

Anto Boritskiy, Mit dem neuen Schlüssel wird die Gültigkeit des Sitzungszeitstempels überprüft. $ sessionData kommt von $ this -> _ data [self :: VALIDATOR_KEY]; Der session_expire_timestamp-Schlüssel wird der Sitzung jedoch nur durch $ this-> getValidatorData () hinzugefügt. Funktion und gespeichert in $ this -> _ data [...] am Ende des Funktionsaufrufs. Daher besteht das Problem darin, dass in vorhandenen Sitzungen dieser session_expire_timestamp-Schlüssel nicht verfügbar ist.
Vaishal Patel
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.