Codeigniter会话用ajax调用

时间:2011-11-02 12:20:37

标签: jquery ajax codeigniter session cookies

我的CodeIgniter应用程序使用会话库并将数据保存到数据库。

我遇到了一些问题,在某个ajax调用之后会创建空白会话。

经过调查,似乎有2个同时发起的函数调用需要进行会话验证。一个人会失败,另一个会没事的。

我能够通过不让它们同时发射来解决这个问题。但我仍然不明白原因为什么它会失败。是否与一次更新用户cookie和第二次呼叫失效有关?或者也许在阅读数据库时它会以某种方式死亡?

我稍微查看了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做得很好。还做了浏览器刷新,它没有超时。不确定为什么会话ID在ajax调用时已经改变,浏览器cookie从未更新过。

9 个答案:

答案 0 :(得分:26)

试试这个

<?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函数中,它在X秒后生成一个新的session_id。每个页面都有一个session_id,如果session_id在进行ajax调用之前到期,则该调用将失败。

在/ application / libraries /中创建一个名为MY_Session(或您设置的任何前缀)的php文件,将此代码粘贴到那里即可。 此函数将覆盖会话类中的sess_update函数,如果该请求是由ajax发出的,则检查每个请求,跳过sess_update函数。

一个坏主意将sess_expiration设置为更高的值。这是一项安全功能,可以保护您免受会话障碍

PD:我的英语不是很流利,如果你不明白,请告诉我。

答案 1 :(得分:5)

在将其合并到稳定分支之前,解决方案(最终!)是使用Areson的commit 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`)
);

有关详细信息,请从上到下阅读pull 1283 comments

答案 2 :(得分:3)

我们遇到了这个问题,这是由于config.php中的sess_time_to_update参数造成的。 CI使用它将会话ID更新为新的会话ID。如果在ajax调用中发生更改,则CI会发送一个新cookie以告知浏览器新的会话ID。不幸的是,浏览器似乎忽略了这个cookie并保留旧的会话ID。

我们通过在配置中将sess_time_to_update设置为sess_expiration来修复它。

$config['sess_time_to_update'] = $config['sess_expiration']; 

答案 3 :(得分:1)

我在codeigniter版本2.1.3中也遇到了这个问题,当我使用以下配置时:

$config['sess_use_database']    = TRUE;

$config['sess_time_to_update']  = 300;

我认为它与ajax请求无关,而是与codeigniter中的错误无关。

当您将会话存储在数据库中时,似乎在300秒后强制注销。经过3个小时的搜索和分析,我发现代码中有一个明显的错误,而且还有一个不清楚的错误,我已经解决了以下错误:

在application / libraries文件夹中创建一个新文件: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
                );
    }   
}


?>

明显的错误是它没有在更新的cookie中存储'user_data'。不明确的错误是它在更新新的会话ID后在文件Session.php中执行函数sess_read(),我不知道为什么会发生这种情况,因为我希望它在更新之前执行而不是像在构造函数中写的那样执行Session.php。所以sess_read()函数开始用旧的会话ID读取旧的cookie信息,并希望将它与数据库中的会话ID进行比较,但是在session_id更新后,它不再存在于数据库中,因此这会导致注销。

Session.php文件的函数sess_read中的这行代码负责读取旧的cookie信息:

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

因此,在MY_Session.php的函数_set_cookie中,我添加了这行代码,用新的代码更新服务器的旧cookie信息:

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

通过此修复,'sess_time_to_update'与'sess_use_database'组合应该可以正常工作。这是一个简单而简单的错误修复。

答案 4 :(得分:0)

使用ajax上传图片时遇到了完全相同的问题,我将配置中的sess_expiration设置为:

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

它解决了我的问题。

答案 5 :(得分:0)

  好的解决方案就在这里。用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
  3. 到解决方案编号“1”我更新了一个小脚本。在使用CI破解了很多之后,我得到的结论是,有两个原因导致CI会话失败。一个是当调用错误的ajax调用时,会话变为UPDATED并且会话丢失;第二个是在糟糕的ajax调用之后它影响CI的SESSION库中的sess_destroy函数。所以我对“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();
    }
    }
    
      

    希望对你们有所帮助

答案 6 :(得分:0)

核心CI会话类处理会话似乎存在缺陷。

找到一个替代会话库,它就像魅力一样。

CI alternate session library

我建议扩展核心CI_Session类而不是替换它。

要扩展,请在MY_Session.php中创建文件application/libraries。粘贴备用库的内容,将class CI_Session替换为class MY_Session extends CI_Session

_flashdata_mark()_flashdata_sweep()_get_time()_set_cookie()_serialize()_unserialize(),{移除受保护的 {1}}函数。

希望它有所帮助。

答案 7 :(得分:0)

似乎仍然有很多旧的CI版本在使用,我想加上我的两分钱,即使这个线程已经老了。我花了几天时间解决Code Igniter中的AJAX调用问题,我有一个解决主要问题的解决方案,尽管有些解决方案并不精彩。我(仍然)使用的CI版本是lhs.swap(rhs)

我的应用程序要求AJAX调用更新last_activity字段以维护有效会话,因此仅仅放弃在AJAX调用上放弃更新会话是不够的。

在这个CI版本中,sess_update和sess_read的错误检查是不合适的(我没有调查过更新的版本),并且很多问题都从那里开始。

第一部分:2.1.3

多个AJAX调用会创建竞争条件,导致数据库锁定以供以后调用。如果我们尝试运行更新查询但是数据库被锁定,我们会收到错误,查询返回false,但cookie仍然使用新数据更新?...很糟糕!此外,对于每个Ajax调用,我们都不需要新的session_id。我们只需要更新last_activity。试试这个:

sess_update()

第2部分: 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次尝试)。此外,我不了解你,但我不希望php因为没有检查错误的查询结果而导致致命错误。如果您想直接尝试此代码,则需要在session.php文件的顶部使用此文件:

sess_read()

另请注意,这不是整个var $sess_query_attempts = 5;功能

sess_read

无论如何,imho还没有完全解决这个问题的答案,我觉得我应该与那些可能仍然在使用大量AJAX的网站上遇到早期会话超时的人分享我的发现。

答案 8 :(得分:-3)

在所有控制器构造函数中编写session_start()