Magento 1 EE v 1.14.3.x (및 CE 1.9.3.x)에서 세션 유효성 검사 실패


18

나는 400-500 방문자와 40-40 주문을 매일 마 젠토 상점을 찾고 있습니다. 최근에 시스템이 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-block은 Magento의 최신 릴리스로 파일에 추가되었습니다. 그리고 이것은 명백한 제동 변화입니다. 자세한 내용은 아래를 참조하십시오.

예외는 하루에 수십 번처럼 자주 발생합니다. 그러나 문자 그대로 위의 조건에 맞지 않으면 예외로 이어지는 조건을 다시 만들 수 없습니다. 예외는 대부분 제품 세부 사항 페이지와 한 페이지 체크 아웃의 마지막 단계에서 발생합니다. 상점은 b2b 상점이며 사용자는 제품 페이지를 보거나 체크 아웃 할 수 있도록 로그인해야합니다. 즉, 세션이 무효화 / 만료되면 사용자가 로그인 페이지로 경로 재 지정됩니다. 현재 결제 중에이 문제를 해결하는 것이 더 중요합니다.

사용자 관점에서 발생하는 일 : 사용자가 장바구니를 채우고 체크 아웃을 진행하고 마지막 단계에 도달 한 후 "주문 제출"버튼을 누르면 아무 일도 일어나지 않습니다. 무대 뒤에서 Magento의 JS는 AJAX 요청을 수행하고 JS는 JSON을 다시 수신 할 것으로 예상하지만이 오류가 발생하면 로그인 페이지의 HTML이 반환되며 JavaScript로 구문 분석 할 수 없으며 아무 것도 수행하지 않습니다. 사용자에게는 매우 혼란 스럽습니다.

글쎄, 그것은 완전한 사용자 시나리오가 아니며, 우리는 사용자들에게 연락하여 장바구니를 채우고 주문을 제출할 때까지 며칠을 기다렸다 고 사람들이 단순히 기억하지 못하기 때문에 정확히 의미하는 것이 무엇인지 알았습니다.

PHP 세션 수명-350000 (최대 4 일) 쿠키 수명-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

고급 디버깅, 세션 추적 및 모든 마술에 대해 생각한 후 문제를 재현하고 그 이유를 이해할 수있었습니다. 나는 약간의 타이밍 그림을 준비했습니다. 아래에서 볼 수 있습니다.

문제 시간

  • 붉은 깃발은 사용자 로그인 및 세션 생성의 순간입니다
  • blue flag는 사용자가 카탈로그 페이지를 여는 순간이며, 열려있는 카테고리 페이지라고 가정합니다.
  • 녹색 깃발은 사용자가 주문을 제출하는 순간입니다 ( /sales/order/save/...요청)

재현하는 방법은 다음과 같습니다.

  1. 시작하기 전에 : PHP 세션 시간 초과 및 Magento 쿠키 시간 초과를 모두 기본 PHP 값인 1440으로 설정하십시오.
  2. 모든 쿠키를 삭제하거나 시크릿 탭을 엽니 다.
  3. Magento 상점으로 이동하여 로그인하십시오 (플래그 1 참조).
  4. 카탈로그를 살펴보고 장바구니에 제품을 추가하십시오 (플래그 2).
  5. 결제를 진행하고 주문을 제출하십시오. 당신이 그것을 한 시간을 주목하십시오. (플래그 3)
  6. 카탈로그를 살펴보고 장바구니에 제품을 추가하십시오 (플래그 4).
  7. magento 쿠키에 대해 구성한 시간 초과가 만료 될 때까지 장바구니 페이지를 새로 고치거나 카탈로그 페이지를 진행하십시오 (플래그 5-6). 플래그 7과 플래그 3 사이의 시간은 쿠키 시간 초과보다 커야합니다.
  8. 결제를 진행하고 주문을 제출하십시오 (Flag 7). 위의 질문에 설명 된 예외로 인해 주문 제출이 실패합니다.

이유:

주어진 요청에 대해서만 인스턴스화되는 특정 세션이 있습니다 (예 : Mage_Rss_Model_Session카탈로그를 탐색하는 동안이 아닌 실제 체크 아웃 중에 만 인스턴스화 됨). 동시에 세션 만료 타임 스탬프는 세션이 인스턴스화 된 경우에만 설정됩니다. 즉, 두 체크 아웃 사이에 충분한 시간이 있고 그 동안 세션이 종료되지 않은 경우 (사용자가 로그 아웃했거나 쿠키가 만료 되었기 때문에) 새로운 Magento 코드는 해당 세션이 유효성 검사를 통과하지 않은 것으로 간주하고 예외를 발생시킵니다. 나를.

어떻게 고치는 지:

글쎄, 나는 몇 가지 옵션이 있습니다.

  1. Magento가 그것에 반응하고 해당 코드를 재고 할 때까지 기다리십시오.
  2. 그동안이 코드를 제거하십시오.
  3. Magento 쿠키 제한 시간을 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를 발견 했습니다. 시도 git하고 incron조합했지만 샌드 박스에서 테스트하고 테스트 한 후에 실제로 디스크 공간이 부족하다는 것을 깨달았습니다.

  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"

추신:

둘 다의 현재 버전

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 지원으로 티켓을 접수했습니다.
Damodar Bashyal

1
@DamodarBashyal 문제는 결제에만 영향을 미치지 않습니다. 제품 페이지에도 영향을 미칩니다. 다른 페이지에도 영향을 줄 수 있다고 생각합니다. 이유-모든 magento 컨트롤러 작업에서 다른 세션 개체 집합이 초기화됩니다. 필요한 경우 자세한 설명을 제공 할 수 있습니다.
Anton Boritskiy

발송물을 만들 때 API에 문제가 있었는데 오류가 발생했습니다. 읽기는 정상이지만 문제는 비활성화 될 때까지 쓰기와 관련된 문제였습니다. 정보를위한 Thx.
Damodar Bashyal

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
프로덕션 환경에서 사용한 경험이 있습니까?
Anton Boritskiy

@AntonBoritskiy 네, 저는 이것을 프로덕션에 사용합니다. 완벽하게 작동합니다.
sv3n

sv3n이 솔루션 방법에 대한 잠재적 인 나쁜 측면이 있습니까?
Vaishal Patel

@VaishalPatel 잠재적으로 나쁜면이 있다면 실제로 보지 못합니다 :) 프로덕션에서 이것을 사용하고 모든 세션 유효성 검사 문제를 해결했습니다. 우려 사항이 있으면 게시하지 않지만 github.com/OpenMage/magento-lts/pull/406으로 문의하십시오 . 어쩌면 일부 SE "프로"도 이것을 검토 할 시간이 있습니까?
sv3n

나는 내 작품을 입을 것이다. 어느 쪽이든 솔루션을 향하여 진행되고 있습니다.
Vaishal Patel

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>


힌트 주셔서 감사합니다-세션을 실제로 저장하는 방법에 대한 정보를 파일에 저장해야합니다. 방금 원래 문제를 알아 냈습니다. 마 젠토 버그라고 생각합니다. 나는 그것을 마무리하고 곧 답변을 게시합니다
Anton Boritskiy

훌륭합니다! ... 내 대답이 솔루션에 전혀 도움이 되었습니까?
gtr1971

정말-내 대답을 참조하십시오
Anton Boritskiy

0

Anton Boritskiy의 세부 사항은 환상적입니다. 그러나이 블록을 제외하는 대신 로컬 복사본을 만들 수 있으므로 코어를 편집하지 않고 다음과 같이 블록을 다시 작성할 수 있습니다.

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가 최근에 이와 같은 몇 가지 버그를 발표했습니다.
Anton Boritskiy 12'2012-12-08

동시에 귀하의 변경으로 인해 원래 문제가 어떻게 해결되는지 잘 모르겠습니다. 좀 더 확장 된 설명을 추가 할 수 있습니까?
Anton Boritskiy

Anto Boritskiy는 목록과 함께 좋은 소리입니다.
Vaishal Patel

Anto Boritskiy, 새 키는 세션 타임 스탬프의 유효성을 검사하는 데 사용됩니다. $ sessionData는 $ this-> _ data [self :: VALIDATOR_KEY]에서 나옵니다. 그러나 session_expire_timestamp 키는 $ this-> getValidatorData ()에 의해서만 세션에 추가됩니다. 함수 호출의 마지막에 $ this-> _ data [...]에 저장됩니다. 따라서 문제는 기존 세션에서이 session_expire_timestamp 키를 사용할 수 없다는 것입니다.
Vaishal Patel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.