Lỗi xác thực phiên trong Magento 1 EE v 1.14.3.x (và CE 1.9.3.x)


18

Tôi đang chăm sóc một cửa hàng Magento với 400-500 khách và 40-50 đơn hàng mỗi ngày. Gần đây hệ thống đã được nâng cấp từ Magento EE 1.14.2.4 lên Magento EE 1.14.3.2 và tôi nhận thấy một số ngoại lệ lạ trong nhật ký:

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

Tôi đã theo đuổi ngoại lệ đó và tôi biết rằng nó đang bị sa thải vì mã xác thực phiên sau không thể xác thực phiên:

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

Khối if này đã được thêm vào tệp với bản phát hành mới nhất từ ​​Magento. Và đây là một sự thay đổi phanh rõ ràng, xem thêm chi tiết dưới đây.

Ngoại lệ xảy ra khá thường xuyên, như một chục lần mỗi ngày. nhưng tôi không thể tạo lại các điều kiện dẫn đến ngoại lệ, trừ khi tôi thực sự đặt điều kiện đúng trong điều kiện trên. Các ngoại lệ thường xảy ra nhất trên các trang chi tiết sản phẩm và ở bước cuối cùng của thanh toán một trang. Cửa hàng là cửa hàng b2b, người dùng phải đăng nhập để xem trang sản phẩm hoặc để có thể thanh toán, có nghĩa là người dùng được chuyển hướng đến các trang đăng nhập khi phiên bị vô hiệu / hết hạn. Hiện tại, điều quan trọng hơn đối với tôi là khắc phục vấn đề này trong quá trình thanh toán.

Điều gì xảy ra từ góc độ người dùng: Người dùng điền vào giỏ hàng, tiến hành thanh toán và đến bước cuối cùng, sau đó anh ấy / cô ấy nhấn nút "gửi đơn đặt hàng" và không có gì xảy ra. Đằng sau hậu trường, JS của Magento thực hiện một yêu cầu AJAX và JS sẽ nhận lại JSON, nhưng nếu lỗi này xảy ra, HTML của trang đăng nhập được trả về, không thể phân tích cú pháp bằng JavaScript và nó không làm gì cả. Đó là siêu khó hiểu cho người dùng.

Chà, đó không phải là kịch bản người dùng hoàn chỉnh, chúng tôi đã liên hệ với người dùng và họ nói với chúng tôi rằng họ đã chờ đợi trong vài ngày giữa việc điền vào giỏ hàng và gửi đơn đặt hàng, điều đó có nghĩa là khó hiểu vì mọi người chỉ đơn giản là không nhớ điều đó.

Tuổi thọ phiên PHP - 350000 (~ 4 ngày trong giây) Tuổi thọ cookie - 345600 (4 ngày)

Đây là câu hỏi thực tế: làm thế nào tôi có thể tìm ra loại hành vi người dùng nào dẫn đến ngoại lệ?

CẬP NHẬT Cho đến nay tôi biết rằng ngoại lệ xảy ra trong các lớp sau theo yêu cầu được thực hiện, với tôi điều đó có nghĩa là không có gì đáng tiếc.

/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

CẬP NHẬT 2 : các phiên được lưu trữ trong các tệp và được dọn sạch bởi trình thu gom rác phiên PHP, cho dù đây có phải là một lựa chọn tốt hay không nằm ngoài phạm vi của câu hỏi này.


Câu trả lời:


24

Sau một số gỡ lỗi nâng cao, theo dõi phiên và suy nghĩ về tất cả các phép thuật đó, tôi đã có thể tái tạo vấn đề và hiểu lý do của nó. Tôi đã chuẩn bị một chút minh họa thời gian, bạn có thể thấy nó dưới đây.

vấn đề thời gian

  • cờ đỏ là thời điểm đăng nhập và tạo phiên của người dùng
  • cờ màu xanh là thời điểm người dùng mở trang danh mục, giả sử đó là trang danh mục được mở.
  • cờ xanh là thời điểm người dùng gửi đơn đặt hàng ( /sales/order/save/...yêu cầu)

Đây là cách tái tạo:

  1. Trước khi bạn bắt đầu: Đặt thời gian chờ phiên PHP và thời gian chờ cookie Magento của cả hai là 1440, là giá trị mặc định của PHP.
  2. Giết tất cả các cookie của bạn hoặc mở tab ẩn danh.
  3. Chuyển đến cửa hàng Magento của bạn và đăng nhập (xem Cờ 1)
  4. Đi qua danh mục và thêm một số sản phẩm vào giỏ hàng (Cờ 2)
  5. Đi qua kiểm tra và gửi đơn đặt hàng. Lưu ý thời gian khi bạn làm điều đó. (Cờ 3)
  6. Đi qua danh mục và thêm một số sản phẩm vào giỏ hàng (Cờ 4)
  7. Tiếp tục làm mới trang giỏ hàng của bạn hoặc đi qua các trang danh mục quá lâu để thời gian chờ mà bạn đã định cấu hình cho cookie magento hết hạn (Cờ 5-6). Lưu ý rằng thời gian giữa Cờ 7 và Cờ 3 phải lớn hơn thời gian chờ cookie.
  8. Đi qua thanh toán và gửi đơn đặt hàng (Cờ 7). Việc gửi đơn đặt hàng sẽ không thành công do ngoại lệ được mô tả trong câu hỏi của tôi ở trên.

Lý do:

Có một số phiên nhất định chỉ được khởi tạo theo yêu cầu đã cho, ví dụ: Mage_Rss_Model_Sessionchỉ được khởi tạo trong quá trình thanh toán thực tế chứ không phải trong khi duyệt qua danh mục. Đồng thời dấu thời gian hết hạn phiên chỉ được đặt khi phiên được khởi tạo. Điều đó có nghĩa là nếu có đủ thời gian giữa hai lần thanh toán và phiên không bị giết trong khi đó (vì người dùng đã đăng xuất hoặc cookie hết hạn), mã Magento mới sẽ coi phiên đó là không vượt qua xác thực và sẽ ném một ngoại lệ, nghe có vẻ lạ tôi.

Làm thế nào để khắc phục:

Chà, tôi có vài lựa chọn:

  1. Đợi cho đến khi Magento phản ứng về điều đó và xem xét lại mã đó.
  2. Loại bỏ mã này trong khi đó.
  3. Hãy thử đặt thời gian chờ cookie Magento thành 0 nếu đó là một tùy chọn cho bạn.

Làm thế nào tôi tìm ra nó:

  1. Tôi bắt đầu với việc thêm đoạn mã sau vào mã gốc của 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
    );

    nó đã cho tôi một cái nhìn sâu sắc về các lớp bị ảnh hưởng và mối tương quan của chúng và bao nhiêu phiên đã hết hạn. Nhưng điều đó không giải thích được tại sao nó xảy ra và hành động nào của người dùng dẫn đến vấn đề.

  2. Sau đó, tôi bắt đầu suy nghĩ làm thế nào tôi có thể theo dõi tất cả các thay đổi đối với dữ liệu phiên và bắt gặp câu hỏi này /superuser/368231/automatic-versioning-upon-file-change-modify-create-delete Tôi đã quyết định đưa ra một thử gitincronkết hợp, nhưng sau khi tôi thực hiện nó và thử nghiệm trong hộp cát, tôi nhận ra rằng tôi sẽ hết dung lượng đĩa rất nhanh trong sản xuất.

  3. Tôi quyết định xây dựng một tập lệnh PHP nhỏ để giải mã dữ liệu phiên và ghi nhật ký cho mỗi lần dừng. Kịch bản này được gọi bởiincron

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

    và đây là incrontabmục tương ứng

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

    đầu ra mẫu

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

Tái bút

Phiên bản hiện tại của cả hai

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

không thể xử lý ngoại lệ trên trong yêu cầu AJAX. Chúng không hiển thị nghĩa đen gì với người dùng, trong khi người dùng thực sự bị đăng xuất!

PPS:

rõ ràng các phiên bản Magento CE 1.9.3.x cũng bị ảnh hưởng, hãy xem https://github.com/OpenMage/magento-mirror/blame/magento-1.9/app/code/core/Mage/Core/Model/Session/ Ab khu / Varien.php

PPPS:

Khi tôi nói "Xóa mã này trong khi đó." Tôi có nghĩa là loại trừ các khối sau

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

bạn có thể làm điều đó với rất nhiều cách, bao gồm:

  1. Chỉ cần xóa bit đó khỏi tệp
  2. Bình luận nó ra
  3. Trở về trước nó
  4. Trở $this->useValidateSessionExpire()về đúng
  5. ...
  6. Đó là lập trình - hãy sáng tạo;)

Tôi chỉ vô hiệu hóa <Mage_Rss>và điều đó đã khắc phục sự cố (khắc phục tạm thời) và đã gửi vé với sự hỗ trợ của magento.
Damodar Bashyal

1
@DamodarBashyal xin lưu ý rằng vấn đề không chỉ ảnh hưởng đến việc thanh toán. Nó cũng ảnh hưởng đến các trang sản phẩm, tôi tin rằng một số trang khác cũng có thể bị ảnh hưởng. Lý do - tập hợp các đối tượng phiên khác nhau được khởi tạo trên mỗi hành động của bộ điều khiển magento. Tôi có thể cung cấp thêm lời giải thích nếu cần thiết.
Anton Boritskiy

Tôi gặp vấn đề với API, khi tạo lô hàng tôi đã gặp lỗi. Đọc là OK nhưng vấn đề là với ghi cho đến khi nó bị vô hiệu hóa. Thx để biết thông tin.
Damodar Bashyal

9

6. Đó là lập trình - hãy sáng tạo;)

Một cách khác để khắc phục điều này (và cải thiện xác thực phiên)

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

Mã phiên hiện lưu trữ dữ liệu trình xác nhận phiên trong mỗi không gian tên và cũng xác thực nó mỗi khi không gian tên được kích hoạt. Điều này là xấu bởi vì:

  1. Vô cùng không hiệu quả của không gian lưu trữ phiên. Dữ liệu trình xác nhận thường bao gồm hơn 50% không gian được sử dụng bởi một không gian tên và khi có nhiều không gian tên, điều này làm tăng thêm một tấn chất thải. Lưu trữ phiên có thể được cắt giảm mạnh với bản vá này và khi sử dụng bộ lưu trữ trong bộ nhớ như Redis hoặc Memcached có vấn đề rất nhiều.
  2. Không hiệu quả của các chu trình tính toán vì nhiều không gian tên có nghĩa là nhiều xác nhận hợp lệ và không có lý do chính đáng nào cho các chu kỳ này khác nhau.
  3. Trên thực tế tạo ra các lỗi như # 394 trong đó dữ liệu trình xác nhận được cập nhật trên một số yêu cầu nhưng không phải là các yêu cầu khác (vì vậy nó có thể khác nhau nhưng không nên). Tôi chưa thử nghiệm nhưng tôi tin rằng điều này cũng sẽ khắc phục vấn đề này.
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()

Nguồn: https://github.com/OpenMage/magento-lts/commit/de06e671c09b375605a956e100911396822e276a


Cập nhật:

Khắc phục cho web/session/use_http_x_forwarded_for optiontùy chọn bị vô hiệu hóa ... https://github.com/OpenMage/magento-lts/pull/457/commits/ec8128b4605e82406679c3cd81244ddf3878c379


1
Điều đó có vẻ thực sự tốt, bất kỳ kinh nghiệm sử dụng điều đó trong sản xuất?
Anton Boritskiy

@AntonBoritskiy Vâng, tôi sử dụng điều này trong sản xuất. Hoạt động hoàn hảo.
sv3n

sv3n có những mặt xấu tiềm tàng nào cho phương pháp giải pháp này không?
Vaishal Patel

@VaishalPatel nếu có bất kỳ mặt xấu tiềm năng nào tôi không thấy chúng thực sự :) Tôi sử dụng điều này trong sản xuất và nó đã giải quyết tất cả các vấn đề xác thực phiên. Tôi sẽ không đăng bài này nếu tôi có bất kỳ lo ngại nào, nhưng nếu bạn có nghi ngờ, vui lòng hỏi tại đây: github.com/OpenMage/magento-lts/pull/406 . Có lẽ một số "ưu" SE cũng có thời gian để xem xét điều này?
sv3n

Tôi sẽ đưa vào sản xuất của tôi. Dù bằng cách nào nó đang tiến tới một giải pháp.
Vaishal Patel

1

Làm thế nào bạn lưu trữ phiên? (tức là trong var / session / hoặc trong DB hoặc sử dụng các công cụ lưu trữ khác như Redis hoặc Memcached)

Cho dù bạn đang sử dụng, hãy đảm bảo quyền ghi của bạn là chính xác var/session/(thường được đặt thành 755 cho các thư mục và 644 cho các tệp) hoặc nếu bạn đang sử dụng Redis hoặc Memcache, hãy đảm bảo các cài đặt kết nối và thời gian chờ của bạn phù hợp với những điều đó .

Inchoo có một hướng dẫn tốt cho Redis: http://inchoo.net/magento/USE-redis-cache-backend-and-session-st Storage-in-magento /

Nếu sử dụng Memcache, hãy kiểm tra bài viết này (tham khảo v1.10, nhưng không nên khác nhiều): http://www.magestore.com/magento/magento-simes-disappear-with-memcache-turned-on.html

Ngoài ra, nếu bạn tình cờ sử dụng một cái gì đó như Varnish, trong quá khứ đã có vấn đề với các phiên mà một số trang nhất định cần phải bấm lỗ.

Cuối cùng, nếu bạn đang sử dụng hệ thống tệp cho các phiên của mình, bạn có thể thấy nhẹ nhõm bằng cách chuyển <session_save>nút trong local.xml"db" thay vì "tệp".

Từ đây <session_save><![CDATA[files]]></session_save>

Để điều này <session_save><![CDATA[db]]></session_save>


cảm ơn về gợi ý - Tôi nên đã thêm thông tin vào câu hỏi về cách tôi thực sự lưu trữ các phiên, tôi lưu trữ chúng trong các tệp. Tôi chỉ tìm ra vấn đề ban đầu, tôi coi đó là một lỗi Magento. Tôi sẽ gói lại và đăng câu trả lời ngay
Anton Boritskiy

Tuyệt vời! ... Câu trả lời của tôi có giúp ích gì cho giải pháp không?
gtr1971

không thực sự - xem câu trả lời của tôi
Anton Boritskiy

0

Các chi tiết của Anton Boritskiy là tuyệt vời. Nhưng thay vì loại trừ khối này, bạn có thể tạo một bản sao cục bộ để bạn không chỉnh sửa lõi và viết lại khối như sau:

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

Điều này đảm bảo rằng việc so sánh giữa time () và session_Exire_timestamp chỉ được thực hiện khi khóa tồn tại và khi tìm thấy phiên không có khóa (tức là phiên 1.9.3 trước), khóa được thêm vào.


Thêm một bản sao cục bộ và ghi đè tất nhiên là tốt hơn sau đó sửa đổi các tệp lõi, chúng tôi duy trì nội bộ danh sách các bản vá được tự động áp dụng trong quá trình xây dựng dự án, vì Magento đã phát hành một vài lỗi như thế gần đây.
Anton Boritskiy

Đồng thời tôi không thấy cách thay đổi của bạn khắc phục vấn đề ban đầu, có thể thêm một chút giải thích mở rộng không?
Anton Boritskiy

Anto Boritskiy đó là một tiếng hét tốt với danh sách.
Vaishal Patel

Anto Boritskiy, Khóa mới được sử dụng để kiểm tra tính hợp lệ của dấu thời gian phiên. $ sessionData xuất phát từ $ this -> _ data [self :: VALIDATOR_KEY]; nhưng khóa session_Exire_timestamp chỉ được thêm vào phiên bởi $ this-> getValidatorData (); hàm và được lưu trữ trong $ this -> _ data [...] ở cuối lệnh gọi hàm. Do đó, vấn đề là trong các phiên hiện tại, khóa session_Exire_timestamp này không khả dụng.
Vaishal Patel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.