Ошибка сеанса Codeigniter с помощью вызовов ajax - PullRequest
23 голосов
/ 02 ноября 2011

Приложение My CodeIgniter использует библиотеку сеансов и сохраняет данные в БД.

У меня возникли проблемы с созданием пустых сеансов после определенного вызова ajax.

По результатам расследования выяснилось, что произошли 2 одновременных вызова функций, которые требуют проверки сеанса. Один потерпит неудачу, а другой будет в порядке.

Мне удалось это исправить, не заставив их одновременно выстрелить. Но я все еще не понимаю ПРИЧИНЫ, почему это терпит неудачу. Связано ли это с тем, что один вызов обновляет куки пользователя, а второй вызов делает недействительным? А может, при чтении БД он как-то умирает?

Я немного просмотрел базовый класс Session и не нашел никаких объяснений причины.

Если бы у кого-то была такая же проблема, прежде чем я был бы признателен за совет о том, как отлаживать или в чем причина.

Спасибо!

РЕДАКТИРОВАТЬ:

Я первоначально сказал, что было возвращение статуса 408. Это был не связанный случай.

Это функция, которая запускает MyVar.refresh () параллельно:

function (event)
{
    var self$ = this.a$;
    var uid  = this.b$.val();
    var tid  = this.c$.val();
    var jqxhr = $.post('/controller1/index',{'uid':uid,'tid':tid,'action':true},function(re)
    {
        if(re.message != 'success')
        {
            MyVar.alert('<span class="msg_error sprite"></span>' + re.error);
            MyVar.refresh();
        } 

    },'json');
    MyVar.refresh();
    return stopDefault(event);
};

ВОЗМОЖНЫЕ РЕШЕНИЯ:

Нашел это: http://codeigniter.com/forums/viewthread/102456/

Очевидно, что это не очень хорошо с AJAX. Одно из решений - запретить обновление сеанса, если это ajax-вызов; Единственная проблема заключается в том, что наш сайт в основном построен на AJAX.

Кроме того, просто понизил sess_time_to_update до чего-то очень частого, и у ajax все было в порядке. Также сделал обновление браузера, и это не время ожидания. Не уверен, почему, если идентификатор сеанса уже изменился после вызова AJAX, а куки браузера никогда не обновлялись.

Ответы [ 9 ]

26 голосов
/ 10 февраля 2012

Попробуйте это

<?php
/**
 * ------------------------------------------------------------------------
 * CI Session Class Extension for AJAX calls.
 * ------------------------------------------------------------------------
 *
 * ====- Save as application/libraries/MY_Session.php -====
 */

class MY_Session extends CI_Session {

    // --------------------------------------------------------------------

    /**
     * sess_update()
     *
     * Do not update an existing session on ajax or xajax calls
     *
     * @access    public
     * @return    void
     */
    public function sess_update()
    {
        $CI = get_instance();

        if ( ! $CI->input->is_ajax_request())
        {
            parent::sess_update();
        }
    }

}

// ------------------------------------------------------------------------
/* End of file MY_Session.php */
/* Location: ./application/libraries/MY_Session.php */

Проблема в функции sess_update класса сеанса, который генерирует новый session_id через X секунд.На каждой странице есть session_id, если истекает session_id до того, как будет выполнен вызов ajax, этот вызов завершится неудачей.

Создайте php-файл в / application / library / с именем MY_Session (или любым другим префиксом, который вы установили),вставьте этот код туда и это все.Эта функция переопределит функцию sess_update в классе сеанса, проверяя при каждом запросе, был ли этот запрос сделан ajax, пропуская функцию sess_update.

Плохая идея устанавливать sess_expiration на более высокие значения.Это функция безопасности, которая защитит вас от перехвата сессии

PD: я не очень хорошо говорю по-английски, если вы чего-то не понимаете, просто дайте мне знать.

5 голосов
/ 09 сентября 2012

До слияния со стабильной ветвью решение (наконец-то!) Заключается в использовании commit Areson * 245bef5 в сочетании со схемой базы данных:

CREATE TABLE IF NOT EXISTS  `ci_sessions` (
    session_id varchar(40) DEFAULT '0' NOT NULL,
    ip_address varchar(45) DEFAULT '0' NOT NULL,
    user_agent varchar(120) NOT NULL,
    last_activity int(10) unsigned DEFAULT 0 NOT NULL,
    user_data text NOT NULL,
    prevent_update int(10) DEFAULT NULL,
    PRIMARY KEY (session_id),
    KEY `last_activity_idx` (`last_activity`)
);

Для получения дополнительной информации читайте тянуть 1283 комментария сверху вниз.

3 голосов
/ 02 ноября 2011

У нас была эта проблема, это было связано с параметром sess_time_to_update в config.php. Я использую это, чтобы обновить идентификатор сеанса до нового. Если изменение происходит при вызове ajax, CI отправляет новый файл cookie, чтобы сообщить браузеру новый идентификатор сеанса. К сожалению, браузеры, похоже, игнорируют этот файл cookie и сохраняют старый идентификатор сессии.

Мы исправили это, установив для sess_time_to_update значение sess_expiration в конфигурации.

$config['sess_time_to_update'] = $config['sess_expiration']; 
1 голос
/ 18 января 2013

У меня тоже была эта проблема в codeigniter версии 2.1.3, когда я использовал следующую конфигурацию:

$config['sess_use_database']    = TRUE;

$config['sess_time_to_update']  = 300;

Я думаю, что это не имеет ничего общего с запросами ajax, а скорее с ошибкой в ​​codeigniter.

Кажется, что когда вы сохраняете сеанс в базе данных, выход из системы происходит через 300 секунд. После 3 часов поиска и анализа я обнаружил явную ошибку в коде, а также неясную, я решил эту ошибку следующим образом:

Создайте новый файл: MY_Session.php в папке приложения / библиотеки

Добавьте следующий код:

<?php
// fixed by sirderno 2013

if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

class MY_Session extends CI_Session
{

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Update an existing session
     *
     * @access  public
     * @return  void
     */
    public function sess_update()
    {
        // We only update the session every five minutes by default
        if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
        {
            return;
        }

        // Save the old session id so we know which record to
        // update in the database if we need it
        $old_sessid = $this->userdata['session_id'];
        $new_sessid = '';
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user's IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // _set_cookie() will handle this for us if we aren't using database sessions
        // by pushing all userdata to the cookie.
        $cookie_data = NULL;

        // Update the session ID and last_activity field in the DB if needed
        if ($this->sess_use_database === TRUE)
        {
            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            $cookie_data['session_id'] = $new_sessid;  // added to solve bug

                    //added to solve bug
            if (!empty($this->userdata['user_data']))
                $cookie_data['user_data'] = $this->userdata['user_data'];

            $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));

        }

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }

    /**
     * Write the session cookie
     *
     * @access  public
     * @return  void
     */
    public function _set_cookie($cookie_data = NULL)
    {
        if (is_null($cookie_data))
        {
            $cookie_data = $this->userdata;
        }

        // Serialize the userdata for the cookie
        $cookie_data = $this->_serialize($cookie_data);

        if ($this->sess_encrypt_cookie == TRUE)
        {
            $cookie_data = $this->CI->encrypt->encode($cookie_data);
        }
        else
        {
            // if encryption is not used, we provide an md5 hash to prevent userside tampering
            $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
        }

        $_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

        $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();

        // Set the cookie
        setcookie(
                    $this->sess_cookie_name,
                    $cookie_data,
                    $expire,
                    $this->cookie_path,
                    $this->cookie_domain,
                    $this->cookie_secure
                );
    }   
}


?>

Явная ошибка заключается в том, что она не сохранила «user_data» в обновленном cookie. Непонятная ошибка в том, что она выполняет функцию sess_read () в файле Session.php после обновления нового идентификатора сеанса, я не знаю, почему это происходит, потому что я ожидал, что он выполняется до обновления, а не после, как написано в конструкторе. Session.php. Таким образом, функция sess_read () начинает считывать старую информацию cookie со старого идентификатора сеанса и хочет сравнить ее с идентификатором сеанса в базе данных, но после обновления session_id ее больше нет в базе данных, поэтому это вызывает выход из системы.

Эта строка кода в функции sess_read файла Session.php отвечает за чтение старой информации о cookie:

$session = $this->CI->input->cookie($this->sess_cookie_name);

Итак, в функцию _set_cookie из MY_Session.php я добавил эту строку кода, чтобы обновить старую информацию о куки-серверах на новую:

$_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

С этим исправлением 'sess_time_to_update' в сочетании с 'sess_use_database' должно работать нормально. Это простое исправление ошибки.

0 голосов
/ 08 февраля 2017

Кажется, все еще используется много старых версий CI, и я хотел добавить свои два цента, хотя эта ветка старая. Я просто потратил несколько дней на решение проблемы вызовов AJAX в Code Igniter, и у меня есть решение, которое охватывает основные проблемы, хотя некоторые решения не являются «замечательными». Версия CI, которую я (все еще) использую: 2.1.3

Мое приложение требует, чтобы вызовы AJAX обновляли поле last_activity для поддержания допустимого сеанса, поэтому для меня недостаточно просто отказаться от обновления сеанса при вызовах AJAX.

Проверка ошибок для sess_update и sess_read неадекватна в этой версии CI (я не исследовал более поздние версии), и многие проблемы начинаются там.

Часть первая: sess_update()

Несколько вызовов AJAX создают условия гонки, которые приводят к блокировке базы данных для последующих вызовов. Если мы пытаемся выполнить запрос на обновление, но база данных заблокирована, мы получаем ошибку, запрос возвращает false, но cookie все еще обновляется новыми данными? ... ПЛОХО! Кроме того, нам не нужен новый session_id для каждого вызова Ajax. Нам нужно только обновить last_activity. Попробуйте это:

    function sess_update()
{
    // We only update the session every five minutes by default
    if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
    {
        return;
    }

    // Save the old session id so we know which record to
    // update in the database if we need it

    $old_sessid = $this->userdata['session_id'];
    //Assume this is an AJAX call... keep the same session_id
    $new_sessid = $old_sessid;

    if( !$this->CI->input->is_ajax_request() ){ 
        //Then create a new session id
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user's IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

    }

    // _set_cookie() will handle this for us if we aren't using database sessions
    // by pushing all userdata to the cookie.
    $cookie_data = NULL;

    // Update the session ID and last_activity field in the DB if needed
    if ($this->sess_use_database === TRUE)
    {

        //TRY THE QUERY FIRST!
        //Multiple simultaneous AJAX calls will not be able to update because the Database will be locked. ( Race Conditions )
        //Besides... We don't want to update the cookie if the database didn't update
        $query = $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
        if( $query ){

            // Update the session data in the session data array
            $this->userdata['session_id'] = $new_sessid;
            $this->userdata['last_activity'] = $this->now;

            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            // Write the cookie
            $this->_set_cookie($cookie_data);

        }else{
            //do nothing... we don't care, we still have an active retreivable session and the update didn't work
            //debug: error_log( "ERROR::" . $this->CI->db->_error_message() ); //Shows locked session database
        }
    }else{
        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }
}

часть 2: sess_read()

Очень похожая проблема здесь ... База данных иногда блокируется во время запроса. За исключением того, что мы не можем игнорировать ошибки на этот раз. Мы пытаемся прочитать сессию, чтобы увидеть, существует ли она ... поэтому, если мы получим ошибку заблокированной базы данных, мы можем проверить ее и повторить попытку (пару раз, если потребуется). В моем тестировании я никогда не делал больше 2 попыток). Кроме того, я не знаю о вас, но я не хочу, чтобы php завершался с ошибкой из-за фатальной ошибки, не проверяя ложный результат запроса. Это понадобится в верхней части файла session.php, если вы хотите попробовать этот код напрямую:

var $sess_query_attempts = 5;

Также обратите внимание, что это не вся функция sess_read

$query = $this->CI->db->get($this->sess_table_name);

//Multiple AJAX calls checking
//But adding add a loop to check a couple more times has stopped premature session breaking
$counter = 0;
while( !$query && $counter < $this->sess_query_attempts     ){

    usleep(100000);//wait a tenth of a second

   $this->CI->db->where('session_id', $session['session_id']);

    if ($this->sess_match_ip == TRUE)
   {
        $this->CI->db->where('ip_address', $session['ip_address']);
    }

    if ($this->sess_match_useragent == TRUE)
    {
        $this->CI->db->where('user_agent', $session['user_agent']);
    }

    $query = $this->CI->db->get($this->sess_table_name);

    $counter++;
}
if ( !$query || $query->num_rows() == 0)
{
    $this->CI->db->where('session_id', $session['session_id']);
    $query = $this->CI->db->get( $this->sess_table_name );

    $this->sess_destroy();
    return FALSE;
}

В любом случае, imho, там нет полного ответа на эту проблему, и я чувствовал, что должен поделиться своими результатами с теми, кто, возможно, все еще испытывает ранние тайм-ауты сеансов на сайтах, которые используют тонны AJAX, как мой.

0 голосов
/ 09 июня 2016

Кажется, есть недостаток в основной сессии сеанса обработки класса CI.

Найдена альтернативная библиотека сеанса, которая работает как чудо.

Библиотека альтернативного сеанса CI

Я бы рекомендовал расширить базовый класс CI_Session, а не заменять его.

Чтобы расширить, создайте файл MY_Session.php в application/libraries.Вставьте содержимое альтернативной библиотеки, замените class CI_Session на class MY_Session extends CI_Session.

Удалить protected из _flashdata_mark(), _flashdata_sweep(), _get_time(), _set_cookie(), _serialize(), _unserialize(), _sess_gc() функции.

Надеюсь, это поможет.

0 голосов
/ 07 июля 2015

напишите session_start() во все ваши конструкторы контроллера

0 голосов
/ 01 апреля 2014

хорошо, хорошие решения здесь. сделать что-нибудь с sess_time_to_update и т. д. попробуйте следующие решения

  1. https://degreesofzero.com/article/fixing-the-expiring-session-problem-in-codeigniter.html
  2. http://ellislab.com/forums/viewthread/138823/#725078

к решению № 1 я обновляю немного сценарий больше. после большого взлома с помощью CI я понимаю, что есть две причины потери CI SESSIONS. один из них - когда выполняются плохие вызовы ajax, сеанс ОБНОВЛЯЕТСЯ, а сеанс теряется; во-вторых, после плохого вызова ajax это влияет на функцию sess_destroy в библиотеке SESSION CI. Так что я сделал небольшое изменение для "1" РЕШЕНИЕ, КОТОРОЕ

/*add this code to MY_Session.php*/     
function sess_destroy()
{
// Do NOT update an existing session on AJAX calls.
if (!$this->CI->input->is_ajax_request())
{
return parent::sess_destroy();
}
/* WHEN USER HIS/HER SELF DO A LOGOUT AND ALSO IF PROGRAMMER SET TO LOGOUT USING AJAX CALLS*/
$firsturlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(1) );        
$securlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(2) );      
if((string)$firsturlseg==(string)'put ur controller name which u are using for login' &&    (string)$securlseg==(string)'put url controler function for logout')
{
 return parent::sess_destroy();
}
}

надеюсь, это поможет вам и людям

0 голосов
/ 05 декабря 2011

У меня была точно такая же проблема при загрузке изображений с помощью ajax, и я установил sess_expiration в конфигурации:

$config['sess_expiration'] = time()+10000000;

И это исправило мою проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...