我的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从未更新过。
答案 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”我更新了一个小脚本。在使用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_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()