Kegagalan validasi sesi di Magento 1 EE v 1.14.3.x (dan CE 1.9.3.x)

18

Saya menjaga toko Magento dengan 400-500 pengunjung dan 40-50 pesanan per hari. Baru-baru ini sistem ditingkatkan dari Magento EE 1.14.2.4 ke Magento EE 1.14.3.2 dan saya melihat beberapa pengecualian aneh dalam log:

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

Saya mengejar pengecualian itu dan saya tahu bahwa itu sedang dipecat karena kode validasi sesi berikut gagal memvalidasi sesi:

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

Blok if ini ditambahkan ke file dengan rilis terbaru dari Magento. Dan ini sepertinya perubahan pengereman, lihat detail lebih lanjut di bawah ini.

Pengecualian terjadi cukup sering, seperti belasan kali per hari. tapi saya tidak dapat membuat ulang kondisi yang mengarah ke pengecualian, kecuali saya benar-benar mengemukakan kondisi di atas. Pengecualian paling sering terjadi pada halaman detail produk dan pada langkah terakhir dari satu checkout halaman. Toko adalah toko b2b, pengguna harus masuk untuk melihat halaman produk atau dapat checkout, berarti pengguna diarahkan ke halaman login saat sesi tidak valid / kedaluwarsa. Saat ini lebih penting bagi saya untuk memperbaiki masalah ini saat checkout.

Apa yang terjadi dari perspektif pengguna: Pengguna mengisi troli, melanjutkan ke checkout, dan mencapai langkah terakhir, kemudian ia menekan tombol "kirim pesanan" dan tidak ada yang terjadi. Di belakang layar JS Magento melakukan permintaan AJAX dan JS mengharapkan untuk menerima JSON kembali, tetapi jika kesalahan ini terjadi, halaman login HTML dikembalikan, yang tidak dapat diuraikan oleh JavaScript dan hanya tidak melakukan apa-apa. Itu sangat membingungkan bagi pengguna.

Ya, itu bukan skenario pengguna yang lengkap, kami menghubungi pengguna dan mereka memberi tahu kami bahwa mereka menunggu beberapa hari antara mengisi keranjang dan mengirimkan pesanan, apa artinya itu sebenarnya sulit diketahui, karena orang tidak ingat itu.

Seumur hidup sesi PHP - 350000 (~ 4 hari dalam hitungan detik) Seumur hidup cookie - 345600 (4 hari)

Inilah pertanyaan aktual: bagaimana saya bisa mengetahui perilaku pengguna seperti apa yang menyebabkan pengecualian?

PEMBARUAN Sejauh ini saya tahu bahwa pengecualian terjadi dalam mengikuti kelas sesuai dengan permintaan yang dibuat, bagi saya itu tidak berarti apa-apa.

/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

PEMBARUAN 2 : sesi disimpan dalam file dan dibersihkan oleh pemungut sampah sesi PHP, apakah ini pilihan yang baik atau tidak berada di luar cakupan pertanyaan ini.

Anton Boritskiy
sumber
Terkait: maxchadwick.xyz/blog/…
Simon

Jawaban:

24

Setelah beberapa debugging lanjutan, sesi pelacakan dan memikirkan semua keajaiban itu saya dapat mereproduksi masalah dan memahami alasannya. Saya menyiapkan sedikit ilustrasi waktu, Anda dapat melihatnya di bawah.

masalah waktu

  • bendera merah adalah momen login pengguna dan pembuatan sesi
  • bendera biru adalah saat ketika pengguna membuka halaman katalog, mari kita asumsikan itu adalah halaman kategori yang dibuka.
  • bendera hijau adalah saat di mana pengguna mengirimkan pesanan ( /sales/order/save/...permintaan)

Berikut cara mereproduksi:

  1. Sebelum Anda mulai: Tetapkan batas waktu sesi PHP dan Batas waktu cookie Magento Anda menjadi 1440 yang merupakan nilai PHP default.
  2. Bunuh semua cookie Anda atau buka tab penyamaran.
  3. Pergi ke toko Magento Anda dan masuk (lihat Bendera 1)
  4. Buka katalog dan tambahkan beberapa produk ke troli (Bendera 2)
  5. Pergi melalui kasir dan kirimkan pesanan. Catat waktu ketika Anda melakukannya. (Bendera 3)
  6. Buka katalog dan tambahkan beberapa produk ke troli (Bendera 4)
  7. Terus menyegarkan halaman kereta Anda atau melalui halaman katalog begitu lama sehingga batas waktu yang telah Anda konfigurasikan untuk cookie magento kedaluwarsa (Bendera 5-6). Perhatikan bahwa waktu antara Bendera 7 dan Bendera 3 harus lebih besar dari batas waktu cookie.
  8. Pergi melalui kasir dan kirimkan pesanan (Bendera 7). Pengiriman pesanan akan gagal karena pengecualian yang dijelaskan dalam pertanyaan saya di atas.

Alasan:

Ada sesi tertentu yang hanya dipakai pada permintaan yang diberikan, misalnya Mage_Rss_Model_Sessionhanya dipakai selama checkout aktual dan tidak saat browsing melalui katalog. Pada saat yang sama, timestamp kadaluwarsa sesi hanya diatur ketika sesi dipakai. Itu berarti bahwa jika ada cukup waktu antara dua checkout dan sesi tidak terbunuh sementara itu (karena pengguna logout atau cookie kedaluwarsa) kode Magento baru akan menganggap sesi itu tidak lulus validasi dan akan mengeluarkan pengecualian, yang entah bagaimana terdengar aneh untuk saya.

Bagaimana cara memperbaiki:

Yah, saya punya beberapa pilihan:

  1. Tunggu sampai Magento bereaksi dan pertimbangkan kembali kode itu.
  2. Hapus kode ini.
  3. Coba atur waktu habis cookie Magento ke 0 jika itu opsi untuk Anda.

Bagaimana saya mengetahuinya:

  1. Saya mulai dengan menambahkan berikut ini ke kode asli 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
    );

    itu memberi saya wawasan yang baik tentang kelas yang terkena dampak dan korelasinya dan berapa banyak sesi yang berakhir. Tapi itu tidak menjelaskan mengapa itu terjadi dan tindakan pengguna mana yang mengarah ke masalah.

  2. Kemudian saya mulai berpikir tentang bagaimana saya bisa melacak semua perubahan pada data sesi dan menemukan pertanyaan ini /superuser/368231/automatic-versioning-upon-file-change-modify-create-delete saya memutuskan untuk memberikan mencoba gitdan incronkombinasi, tetapi setelah saya menerapkannya dan diuji di sandbox, saya menyadari bahwa saya akan kehabisan ruang disk yang sangat cepat dalam produksi.

  3. Saya memutuskan untuk membuat skrip PHP kecil yang akan men-decode data sesi dan menulis log untuk setiap sesi. Script ini dipanggil olehincron

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

    dan di sini adalah incrontabentri yang sesuai

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

    output sampel

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

Versi saat ini keduanya

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

tidak dapat menangani pengecualian di atas selama permintaan AJAX. Mereka benar-benar tidak menampilkan apa pun kepada pengguna, sementara pengguna keluar secara efektif!

PPS:

rupanya versi Magento CE 1.9.3.x juga terpengaruh, lihat https://github.com/OpenMage/magento-mirror/blame/magento-1.9/app/code/core/Mage/Core/Model/Session/Abstract/ Varien.php

PPPS:

Ketika saya mengatakan "Hapus kode ini." Maksud saya mengecualikan blok berikut

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

Anda dapat melakukannya dengan banyak cara, termasuk:

  1. Cukup hapus bit itu dari file
  2. Mengomentari itu
  3. Kembali sebelum itu
  4. Membuat $this->useValidateSessionExpire()pengembalian menjadi benar
  5. ...
  6. Ini pemrograman - menjadi kreatif;)
Anton Boritskiy
sumber
Saya baru saja menonaktifkan <Mage_Rss>dan yang memperbaiki masalah (perbaikan sementara) dan telah mengajukan tiket dengan dukungan magento.
Damodar Bashyal
1
@DamodarBashyal perlu diketahui bahwa masalah tidak hanya memengaruhi checkout. Ini juga mempengaruhi halaman produk, saya percaya bahwa beberapa halaman lain mungkin juga terpengaruh. Alasan - sekumpulan objek sesi berbeda diinisialisasi pada setiap aksi pengontrol magento. Saya dapat memberikan lebih banyak penjelasan jika diperlukan.
Anton Boritskiy
Saya memiliki masalah dengan API, ketika membuat pengiriman saya mendapatkan kesalahan. Baca baik-baik saja tetapi masalah dengan tulis hingga dinonaktifkan. Terima kasih untuk info.
Damodar Bashyal
9

6. Ini pemrograman - menjadi kreatif;)

Cara lain untuk memperbaikinya (dan meningkatkan validasi sesi)

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

Kode sesi saat ini menyimpan data validator sesi dalam setiap namespace dan juga memvalidasinya setiap kali namespace dimasukkan. Ini buruk karena:

  1. Ruang penyimpanan sesi sangat tidak efisien. Data validator sering kali terdiri atas lebih dari 50% ruang yang digunakan oleh namespace dan ketika ada banyak ruang nama ini menambahkan hingga satu ton limbah. Penyimpanan sesi dapat dipotong secara drastis dengan tambalan ini dan saat menggunakan penyimpanan dalam memori seperti Redis atau Memcached yang sangat berarti.
  2. Siklus komputasi yang tidak efisien karena beberapa ruang nama berarti banyak validasi dan tidak ada alasan yang baik untuk ini berbeda satu sama lain.
  3. Sebenarnya menciptakan bug seperti # 394 mana data validator diperbarui pada beberapa permintaan tetapi tidak pada yang lain (sehingga bisa berbeda tetapi tidak seharusnya). Saya belum menguji tetapi saya yakin ini juga akan memperbaiki masalah ini.
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()

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


Memperbarui:

Perbaiki untuk web/session/use_http_x_forwarded_for optionopsi yang dinonaktifkan ... https://github.com/OpenMage/magento-lts/pull/457/commits/ec8128b4605e82406679c3cd81244ddf3878c379

sv3n
sumber
1
yang terlihat bagus sebenarnya, ada pengalaman menggunakannya dalam produksi?
Anton Boritskiy
@AntonBoritskiy Ya, saya menggunakan ini dalam produksi. Bekerja dengan sempurna.
sv3n
sv3n apakah ada sisi buruk potensial dari metode solusi ini?
Vaishal Patel
@VaishalPatel jika ada potensi sisi buruk yang saya tidak melihatnya sebenarnya :) Saya menggunakan ini pada produksi dan menyelesaikan semua masalah validasi sesi. Saya tidak akan memposting ini jika saya memiliki masalah, tetapi jika Anda memiliki keraguan silakan tanyakan di sini: github.com/OpenMage/magento-lts/pull/406 . Mungkin beberapa "pro" SE memiliki waktu untuk mengulas ini juga?
sv3n
Saya akan memasukkan ke produksi saya. Apa pun itu berkembang menuju solusi.
Vaishal Patel
1

Bagaimana Anda menyimpan sesi? (mis. dalam var / session / atau dalam DB, atau menggunakan mesin caching lainnya seperti Redis atau Memcached)

Apa pun yang Anda gunakan, pastikan izin menulis Anda sudah benar var/session/(biasanya diatur ke 755 untuk dir dan 644 untuk file), atau jika Anda menggunakan Redis atau Memcache, pastikan pengaturan koneksi dan batas waktu Anda baik untuk .

Inchoo memiliki tutorial yang bagus untuk Redis: http://inchoo.net/magento/using-redis-cache-backend-and-session-storage-in-magento/

Jika menggunakan Memcache, periksa artikel ini (merujuk pada v1.10, tetapi seharusnya tidak jauh berbeda): http://www.magestore.com/magento/magento-sessions-disappearing-with-memcache-turned-on.html

Juga, jika Anda kebetulan menggunakan sesuatu seperti Varnish, ada masalah di masa lalu dengan sesi di mana lubang-lubang halaman tertentu diperlukan.

Terakhir, jika Anda menggunakan sistem file untuk sesi Anda, Anda mungkin merasa lega hanya dengan mengalihkan <session_save>node di local.xml"db" alih-alih "file".

Dari ini <session_save><![CDATA[files]]></session_save>

Untuk ini <session_save><![CDATA[db]]></session_save>

gtr1971
sumber
terima kasih atas petunjuknya - Saya seharusnya menambahkan info ke pertanyaan tentang bagaimana cara saya menyimpan sesi sebenarnya, saya menyimpannya dalam file. Saya baru saja menemukan masalah aslinya, saya menganggapnya sebagai bug Magento. Saya akan membungkusnya dan mengirim jawaban segera
Anton Boritskiy
Hebat! ... Apakah jawaban saya membantu sama sekali dengan solusinya?
gtr1971
tidak juga - lihat jawaban saya
Anton Boritskiy
0

Detail oleh Anton Boritskiy fantastis. Tetapi alih-alih mengecualikan blok ini Anda bisa membuat salinan lokal sehingga Anda tidak mengedit inti dan menulis ulang blok seperti:

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

Ini memastikan bahwa perbandingan antara waktu () dan session_expire_timestamp hanya dieksekusi ketika kunci ada dan bahwa ketika sesi ditemukan yang tidak memiliki kunci (yaitu sesi pra 1.9.3) kunci ditambahkan.

Vaishal Patel
sumber
Menambahkan salinan lokal dan pengesampingan tentu saja jauh lebih baik daripada memodifikasi file inti, kami secara internal memelihara daftar tambalan yang secara otomatis diterapkan selama pembangunan proyek, menyebabkan Magento merilis beberapa bug seperti itu baru-baru ini.
Anton Boritskiy
Pada saat yang sama saya tidak melihat bagaimana perubahan Anda memperbaiki masalah awal, dapat menambahkan sedikit penjelasan lebih luas?
Anton Boritskiy
Anto Boritskiy yang berteriak dengan daftar.
Vaishal Patel
Anto Boritskiy, Kunci baru digunakan untuk memeriksa validitas cap waktu sesi. $ sessionData berasal dari $ this -> _ data [self :: VALIDATOR_KEY]; tetapi kunci session_expire_timestamp hanya ditambahkan ke sesi oleh $ this-> getValidatorData (); berfungsi dan disimpan dalam $ this -> _ data [...] di akhir panggilan fungsi. Jadi masalahnya adalah bahwa dalam sesi yang ada kunci session_expire_timestamp ini tidak tersedia.
Vaishal Patel