Errore di convalida della sessione in Magento 1 EE v 1.14.3.x (e CE 1.9.3.x)


18

Mi occupo di un negozio Magento con 400-500 visitatori e 40-50 ordini al giorno. Recentemente il sistema è stato aggiornato da Magento EE 1.14.2.4 a Magento EE 1.14.3.2 e ho notato alcune strane eccezioni nei log:

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

Stavo inseguendo quell'eccezione e so che è stata attivata perché il seguente codice di convalida della sessione non riesce a convalidare la sessione:

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

Questo if-block è stato aggiunto al file con l'ultima versione di Magento. E questo è apparentemente un cambiamento di frenata, vedi più dettagli sotto.

L'eccezione si verifica abbastanza spesso, come una dozzina di volte al giorno. ma non sono in grado di ricreare condizioni che portano all'eccezione, a meno che non metta letteralmente vero nella condizione sopra. Le eccezioni si verificano più spesso nelle pagine dei dettagli del prodotto e nell'ultimo passaggio di una pagina di verifica. Il negozio è un negozio b2b, l'utente deve accedere per vedere la pagina del prodotto o per poter effettuare il checkout, significa che l'utente viene reindirizzato alle pagine di accesso quando la sessione è invalidata / scaduta. Al momento è più importante per me risolvere questo problema durante il checkout.

Cosa succede dal punto di vista dell'utente: l'utente riempie il carrello, procede al checkout e raggiunge l'ultimo passaggio, quindi preme il pulsante "invia l'ordine" e non succede nulla. Dietro le quinte Magento JS esegue una richiesta AJAX e JS prevede di ricevere nuovamente JSON, ma se si verifica questo errore viene restituito l'HTML della pagina di accesso, che non può essere analizzato da JavaScript e non fa proprio nulla. Questo è super confuso per gli utenti.

Bene, questo non è uno scenario utente completo, abbiamo contattato gli utenti e ci hanno detto che hanno aspettato alcuni giorni tra il riempimento del carrello e l'invio dell'ordine, ciò che significa esattamente è difficile da capire, perché le persone semplicemente non lo ricordano.

Durata della sessione PHP - 350000 (~ 4 giorni in secondi) Durata dei cookie - 345600 (4 giorni)

Ecco la vera domanda: come posso scoprire quale tipo di comportamento dell'utente porta all'eccezione?

AGGIORNAMENTO Finora so che l'eccezione si verifica nelle seguenti classi in base alla richiesta fatta, per me questo purtroppo non significa nulla.

/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

AGGIORNAMENTO 2 : le sessioni vengono archiviate in file e ripulite dal garbage collector di sessioni PHP, indipendentemente dal fatto che questa sia una buona scelta o meno, non rientra nell'ambito di questa domanda.


Risposte:


24

Dopo un po 'di debug avanzato, traccia delle sessioni e pensando a tutta quella magia sono stato in grado di riprodurre il problema e comprenderne il motivo. Ho preparato una piccola illustrazione dei tempi, la puoi vedere qui sotto.

tempo problematico

  • la bandiera rossa è il momento del login dell'utente e della creazione della sessione
  • la bandiera blu è il momento in cui l'utente apre la pagina del catalogo, supponiamo che sia una pagina di categoria che viene aperta.
  • bandiera verde è il momento in cui l'utente invia l'ordine ( /sales/order/save/...richiesta)

Ecco come riprodurre:

  1. Prima di iniziare: imposta il timeout della sessione PHP e il timeout dei cookie Magento su 1440, che è un valore PHP predefinito.
  2. Uccidi tutti i cookie o apri la scheda in incognito.
  3. Vai al tuo negozio Magento e accedi (vedi Bandiera 1)
  4. Sfoglia il catalogo e aggiungi alcuni prodotti al carrello (Flag 2)
  5. Vai alla cassa e invia un ordine. Nota l'ora in cui l'hai fatto. (Bandiera 3)
  6. Sfoglia il catalogo e aggiungi alcuni prodotti al carrello (Flag 4)
  7. Continua ad aggiornare la tua pagina del carrello o ad esplorare le pagine del catalogo così a lungo che scade il timeout che hai configurato per i cookie magento (Flag 5-6). Si noti che il tempo tra Flag 7 e Flag 3 dovrebbe essere maggiore del timeout del cookie.
  8. Vai alla cassa e invia un ordine (Flag 7). L'invio dell'ordine non riuscirà a causa dell'eccezione descritta nella mia domanda precedente.

Motivo:

Esistono alcune sessioni che vengono istanziate solo su determinate richieste, ad esempio Mage_Rss_Model_Sessionviene istanziato solo durante il checkout effettivo e non durante la navigazione nel catalogo. Allo stesso tempo, il timestamp di scadenza della sessione viene impostato solo quando la sessione è stata istanziata. Ciò significa che se nel frattempo c'è stato abbastanza tempo tra due checkout e la sessione non è stata interrotta (perché l'utente si è disconnesso o il cookie è scaduto), il nuovo codice Magento considererà quella sessione come non valida e genererà un'eccezione, il che suona in qualche modo strano me.

Come risolvere:

Bene, ho alcune opzioni:

  1. Aspetta che Magento reagisca e riconsidera quel codice.
  2. Rimuovi questo codice nel frattempo.
  3. Prova a impostare il timeout del cookie Magento su 0 se questa è un'opzione per te.

Come l'ho capito:

  1. Ho iniziato con l'aggiunta di quanto segue al codice originale di 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
    );

    mi ha dato una buona idea delle classi interessate e della loro correlazione e della durata della sessione. Ma ciò non spiega perché ciò accada e quali azioni dell'utente portano al problema.

  2. Poi ho iniziato a pensare a come posso tracciare tutte le modifiche ai dati della sessione e ho trovato questa domanda /superuser/368231/automatic-versioning-upon-file-change-modify-create-delete che ho deciso di dare un tentativo gite una incroncombinazione, ma dopo averlo implementato e testato in sandbox, mi sono reso conto che avrei esaurito lo spazio su disco molto velocemente in produzione.

  3. Ho deciso di creare un piccolo script PHP che decodificherà i dati della sessione e scriverà i log per ogni sessione. Questo script è stato chiamato daincron

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

    e qui è la incrontabvoce corrispondente

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

    uscita campione

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

Versioni attuali di entrambi

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

non sono in grado di gestire l'eccezione sopra durante la richiesta AJAX. Non mostrano letteralmente nulla all'utente, mentre l'utente viene effettivamente disconnesso!

PPS:

apparentemente anche le versioni di Magento CE 1.9.3.x sono interessate, vedi https://github.com/OpenMage/magento-mirror/blame/magento-1.9/app/code/core/Mage/Core/Model/Session/Abstract/ Varien.php

PPPS:

Quando ho detto "Rimuovi questo codice nel frattempo." Intendevo escludere il seguente blocco

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

puoi farlo in tanti modi, tra cui:

  1. Semplicemente eliminando quel bit dal file
  2. Commentandolo
  3. Tornando prima
  4. Rendere $this->useValidateSessionExpire()vero il ritorno
  5. ...
  6. È programmazione - sii creativo;)

Ho appena disabilitato <Mage_Rss>e questo ha risolto il problema (correzione temporanea) e ho presentato il ticket con il supporto di Magento.
Damodar Bashyal,

1
@DamodarBashyal, tieni presente che il problema non riguarda solo il checkout. Colpisce anche le pagine dei prodotti, credo che alcune altre pagine potrebbero essere interessate. Motivo: ogni serie di oggetti controller di magento viene inizializzata su ogni serie di oggetti sessione. Posso fornire ulteriori spiegazioni se necessario.
Anton Boritskiy,

Ho avuto problemi con l'API, durante la creazione della spedizione stavo ricevendo un errore. La lettura era corretta ma il problema riguardava la scrittura fino a quando non veniva disabilitato. Grazie per info.
Damodar Bashyal,

9

6. Sta programmando: sii creativo;)

Un altro modo per risolvere questo problema (e migliorare la convalida della sessione)

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

Il codice di sessione attualmente memorizza i dati del validatore di sessione all'interno di ogni spazio dei nomi e li convalida ogni volta che lo spazio dei nomi viene inserito. Questo è negativo perché:

  1. Spazio di archiviazione della sessione estremamente inefficiente. I dati del validatore spesso comprendono oltre il 50% dello spazio utilizzato da uno spazio dei nomi e quando ci sono molti spazi dei nomi questo si aggiunge a una tonnellata di rifiuti. L'archiviazione della sessione può essere drasticamente ridotta con questa patch e quando si utilizza un archivio in memoria come Redis o Memcached che conta molto.
  2. Inefficiente dei cicli di calcolo poiché più spazi dei nomi significano più convalide e non vi è alcuna ragione per cui queste differiscano l'una dall'altra.
  3. In realtà crea bug come # 394 in cui i dati del validatore vengono aggiornati su alcune richieste ma non su altre (quindi può differire ma non dovrebbe). Non ho testato ma credo che questo risolverà anche questo problema.
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()

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


Aggiornare:

Correzione web/session/use_http_x_forwarded_for optiondell'opzione disabilitata ... https://github.com/OpenMage/magento-lts/pull/457/commits/ec8128b4605e82406679c3cd81244ddf3878c379


1
sembra davvero buono, qualche esperienza nell'usarlo in produzione?
Anton Boritskiy,

@AntonBoritskiy Sì, lo uso in produzione. Funziona perfettamente
sv3n,

sv3n ci sono potenziali lati negativi di questo metodo di soluzione?
Vaishal Patel,

@VaishalPatel se ci sono potenziali lati negativi non li vedo in realtà :) Lo uso sulla produzione e ho risolto tutti i problemi di convalida della sessione. Non pubblicherei questo post se avessi dubbi, ma se hai dei dubbi chiedi qui: github.com/OpenMage/magento-lts/pull/406 . Forse alcuni dei "professionisti" dell'ES hanno del tempo per recensire anche questo?
sv3n,

Metterò sulla mia produzione. In entrambi i casi sta progredendo verso una soluzione.
Vaishal Patel,

1

Come stai memorizzando le sessioni? (ad es. in var / session / o nel DB o utilizzando altri motori di memorizzazione nella cache come Redis o Memcached)

Qualunque cosa tu stia utilizzando, assicurati che le tue autorizzazioni di scrittura siano corrette var/session/(in genere impostate su 755 per dirs e 644 per i file), o se stai usando Redis o Memcache, assicurati che le impostazioni di connessione e timeout siano valide per quelle .

Inchoo ha un buon tutorial per Redis: http://inchoo.net/magento/using-redis-cache-backend-and-session-storage-in-magento/

Se usi Memcache, consulta questo articolo (fa riferimento alla v1.10, ma non dovrebbe essere molto diverso): http://www.magestore.com/magento/magento-sessions-disappearing-with-memcache-turned-on.html

Inoltre, se ti capita di usare qualcosa come Varnish, ci sono stati problemi in passato con sessioni in cui era necessario perforare alcune pagine.

Infine, se stai usando il file system per le tue sessioni, potresti trovare sollievo semplicemente cambiando il <session_save>nodo in local.xml"db" invece di "file".

Da questo <session_save><![CDATA[files]]></session_save>

A questo <session_save><![CDATA[db]]></session_save>


grazie per il suggerimento - Avrei dovuto aggiungere le informazioni alla domanda su come immagazzino effettivamente le sessioni, le immagazzino in file. Ho appena capito il problema originale, lo considero un bug di Magento. Lo avvolgerò e pubblicherò una risposta a breve
Anton Boritskiy,

Fantastico! ... La mia risposta mi è stata di grande aiuto con la soluzione?
gtr1971,

non proprio - vedi la mia risposta
Anton Boritskiy il

0

Il dettaglio di Anton Boritskiy è fantastico. Ma invece di escludere questo blocco potresti creare una copia locale in modo da non modificare il core e riscrivere il blocco come:

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

Questo assicura che il confronto tra time () e session_expire_timestamp viene eseguito solo quando esiste la chiave e che quando viene trovata una sessione che non ha la chiave (cioè una sessione pre 1.9.3) la chiave viene aggiunta.


L'aggiunta di una copia locale e l'override sono ovviamente molto meglio della modifica dei file core, manteniamo internamente un elenco di patch che vengono automaticamente applicate durante la creazione del progetto, perché Magento ha rilasciato un paio di bug come quello di recente.
Anton Boritskiy il

Allo stesso tempo non vedo come la tua modifica risolva il problema originale, potrebbe aggiungere una spiegazione più estesa?
Anton Boritskiy il

Anto Boritskiy è un bel grido con la lista.
Vaishal Patel,

Anto Boritskiy, La nuova chiave viene utilizzata per verificare la validità del timestamp della sessione. $ sessionData proviene da $ this -> _ data [self :: VALIDATOR_KEY]; ma la chiave session_expire_timestamp viene aggiunta alla sessione solo da $ this-> getValidatorData (); funzione e memorizzata in $ this -> _ data [...] alla fine della chiamata di funzione. Pertanto, il problema è che nelle sessioni esistenti questa chiave session_expire_timestamp non è disponibile.
Vaishal Patel,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.