【问题标题】:Codeigniter session bugging out with ajax callsCodeigniter 会话因 ajax 调用而出错
【发布时间】:2011-12-20 06:43:12
【问题描述】:

我的 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 从未更新。

【问题讨论】:

  • HTTP 408 是请求超时,当服务器停止等待来自浏览器的预期输入时返回。如果不知道您的 ajax 调用是什么样子或者您的控制器对它们做了什么,您的问题就无法回答。
  • 哪种方法适合你?
  • @Zabs 这可能会有所帮助。我正在使用这个线程的解决方案:github.com/EllisLab/CodeIgniter/pull/1900
  • 如果以下答案无效,请检查此问题:stackoverflow.com/questions/24297447/… 这是一个会话竞赛条件(在此处查看我的答案,并阅读hiretheworld 人员发布的文章,非常好且内容丰富)跨度>

标签: jquery ajax codeigniter session cookies


【解决方案1】:

试试这个

<?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:我的英语不是很流利,如果你不明白,请告诉我。

【讨论】:

  • 警告。 将 CI 实例分配给变量时,请使用 =&amp; 而不是 =。通过引用分配允许您使用原始 CodeIgniter 对象,而不是创建它的副本。鉴于会话的使用频率很高,这目前是内存占用。
  • 正是我想要的!这项工作就像一个魅力,谢谢!
  • 等等,这个文件my_session.php会被自动调用吗?我不需要在某个地方调用这个库?
【解决方案2】:

直到它被合并到stable分支中,解决方案(终于!)是使用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自上而下。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 您使用的是哪个版本的 CI?为什么 ajax 调用会创建新的 cookie 或会话?
    • 另外,这不是让用户在达到 sess_expiration 时间后超时吗?比如过期2小时,用户已经活跃2小时,last_activity时间永远不会更新,2小时后用户会被强制超时。
    • sess_time_to_update 和 sess_expirations 都是根据 last_activity 计算的,因此将它们设置为相同的值会使会话在更新之前过期。
    • sess_time_to_update 用于会话类的 sess_update 函数中。这是在达到超时时更改用户 cookie 中的键的函数,在您的情况下是在 AJAX 调用中。
    • 我希望只需将 $config['sess_expiration'] 设置为小于 $config['sess_time_to_update'] 的值就足够了.
    【解决方案4】:

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

    $config['sess_use_database']    = TRUE;
    
    $config['sess_time_to_update']  = 300;
    

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

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

    在应用程序/库文件夹中创建一个新文件: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 中。不清楚的错误是它在更新新的会话 id 后执行文件 Session.php 中的函数 sess_read(),我不知道为什么会发生这种情况,因为我希望它在更新之前执行,而不是在构造函数中写入之后执行Session.php。所以 sess_read() 函数开始用旧的 session id 读取旧的 cookie 信息,并想将它与数据库中的 session 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”结合使用应该可以正常工作。这是一个简单明了的错误修复。

    【讨论】:

    • 你向 CI 的 Github 报告了这个问题吗?如果这是一个真正的错误,我会建议这样做。
    • 不,还没有。我会查找我可以在哪里做这个错误报告,我很确定这是一个错误。
    • 其他人试过这个吗?我有类似的问题,所以会试一试 - ps 这个错误是否已提交到 Github 进行修复?
    【解决方案5】:

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

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

    它解决了我的问题。

    【讨论】:

    • 我不想不使用会话过期。这行不通。
    【解决方案6】:

    好的解决方案就在这里。用 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 调用之后,它会影响 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();
    }
    }
    

    希望对大家也有帮助

    【讨论】:

      【解决方案7】:

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

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

      CI alternate session library

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

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

      _flashdata_mark()_flashdata_sweep()_get_time()_set_cookie()_serialize()_unserialize()_sess_gc() 函数中删除 protected

      希望对你有帮助。

      【讨论】:

        【解决方案8】:

        似乎仍然有很多旧的 CI 版本在使用,我想加两分钱,即使这个线程很旧。我刚刚花了几天时间解决 Code Igniter 中的 AJAX 调用问题,我有一个涵盖主要问题的解决方案,尽管有些解决方案并不“出色”。我(仍在)使用的 CI 版本是2.1.3

        我的应用程序要求 AJAX 调用更新 last_activity 字段以维持有效的会话,因此简单地放弃更新 AJAX 调用上的会话对我来说是不够的。

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

        第一部分:sess_update()

        多个 AJAX 调用会产生竞争条件,从而导致数据库被锁定以供以后调用。如果我们尝试运行更新查询但数据库被锁定,我们会收到错误,查询返回 false,但 cookie 仍然使用新数据更新?... 糟糕!此外,我们不需要为每个 Ajax 调用创建一个新的 session_id。我们只需要更新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;
        }
        

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

        【讨论】:

          【解决方案9】:

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

          【讨论】:

          • 这与 CodeIgniter 会话无关
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-23
          • 1970-01-01
          相关资源
          最近更新 更多