Magento 1 EE v 1.14.3.x(およびCE 1.9.3.x)でのセッション検証の失敗


18

400〜500人の訪問者と1日あたり40〜50件の注文があるMagentoショップを探しています。最近、システムが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からの最新リリースでファイルに追加されました。これは明らかにブレーキの変更です。詳細は以下をご覧ください。

例外は非常に頻繁に発生し、1日に数十回です。しかし、上記の条件に文字通りtrueを設定しない限り、例外につながる条件を再作成することはできません。例外はほとんどの場合、製品詳細ページと1ページのチェックアウトの最後のステップで発生します。ショップはb2bショップです。製品ページを表示したり、チェックアウトしたりするには、ユーザーがログインする必要があります。つまり、セッションが無効化/期限切れになると、ユーザーはログインページにリダイレクトされます。現時点では、チェックアウト中にこの問題を修正することがより重要です。

ユーザーの観点から何が起こるか:ユーザーはカートを満杯にし、チェックアウトに進み、最後のステップに進み、「注文を送信」ボタンを押しても何も起こりません。舞台裏では、MagentoのJSは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セッションガベージコレクターによってクリーンアップされます。これが適切な選択かどうかは、この質問の範囲外です。


回答:


24

高度なデバッグ、セッショントレース、すべての魔法について考えた後、問題を再現し、その理由を理解することができました。私は少しタイミング図を用意しました、あなたはそれを以下で見ることができます。

問題の時間

  • 赤い旗はユーザーのログインとセッションの作成の瞬間です
  • 青い旗は、ユーザーがカタログページを開いた瞬間です。カタログページが開かれていると仮定しましょう。
  • 緑の旗は、ユーザーが注文(/sales/order/save/...リクエスト)を送信した瞬間です

再現方法は次のとおりです。

  1. 開始する前に:PHPセッションタイムアウトとMagento Cookieタイムアウトの両方を、デフォルトのPHP値である1440に設定します。
  2. すべてのCookieを削除するか、シークレットタブを開きます。
  3. Magentoショップにアクセスしてログインします(フラグ1を参照)
  4. カタログを調べて、いくつかの製品をカートに追加します(フラグ2)
  5. チェックアウトを行って注文を送信します。行った時間に注意してください。(フラグ3)
  6. カタログを調べて、いくつかの製品をカートに追加します(フラグ4)
  7. magento Cookieに対して設定したタイムアウトが期限切れになるまで、カートページの更新またはカタログページの閲覧を続けてください(フラグ5〜6)。フラグ7とフラグ3の間の時間は、Cookieのタイムアウトよりも長くする必要があることに注意してください。
  8. チェックアウトを行って注文を送信します(フラグ7)。上記の私の質問で説明した例外のため、注文の送信は失敗します。

理由:

特定のリクエストでのみインスタンス化される特定のセッションがあります。たとえば Mage_Rss_Model_Session、カタログの閲覧中ではなく、実際のチェックアウト中にのみインスタンス化されます。同時に、セッションの有効期限のタイムスタンプは、セッションがインスタンス化されたときにのみ設定されます。つまり、2つのチェックアウトの間に十分な時間があり、その間にセッションが強制終了されなかった場合(ユーザーがログアウトまたはCookieが期限切れになったため)、新しいMagentoコードはそのセッションを検証に合格しないと見なし、例外をスローします。私。

直し方:

まあ、私はいくつかのオプションがあります:

  1. Magentoがそれに反応し、そのコードを再考するまで待ちます。
  2. その間、このコードを削除してください。
  3. それがあなたのためのオプションであるならば、Magento cookieタイムアウトを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 私は与えることにしましたしようgitincronの組み合わせが、私はそれを実装し、サンドボックスでテストした後、私は生産に本当に速いディスク領域が不足することを実現。

  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/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に問い合わせてください。たぶん、SEの「プロ」の中にはこれをレビューする時間がある人もいるかもしれません。
sv3n

私は自分の制作に取りかかります。いずれにせよ、解決に向かって進んでいます。
ヴァイシャルパテル

1

セッションをどのように保存していますか?(つまり、var / session /またはDB内、またはRedisやMemcachedなどの他のキャッシュエンジンを使用)

どちらを使用している場合でも、書き込み許可が正しいことを確認してくださいvar/session/(通常、dirsが755、ファイルが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-sessions-disappearing-with-memcache-turned-on.html

また、ワニスのようなものを使用している場合、特定のページに穴を開ける必要があるセッションで過去に問題がありました。

最後に、セッションにファイルシステムを使用している場合、「files」ではなく「db」に<session_save>ノードを切り替えるだけで安心local.xmlできます。

これから <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は最近そのようなバグをいくつかリリースしました。
アントンボリツキー

同時に、変更によって元の問題がどのように修正されるかわかりませんが、もう少し説明を追加できますか?
アントンボリツキー

アント・ボリツキーは、このリストで良いコメントをしています。
ヴァイシャルパテル

Anto Boritskiy、新しいキーは、セッションのタイムスタンプの有効性の確認に使用されます。$ sessionDataは$ this-> _ data [self :: VALIDATOR_KEY]から取得します。ただし、session_expire_timestampキーは、$ this-> getValidatorData()によってのみセッションに追加されます。関数であり、関数呼び出しの最後に$ this-> _ data [...]に保存されます。したがって、問題は、既存のセッションではこのsession_expire_timestampキーが利用できないことです。
ヴァイシャルパテル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.