web-dev-qa-db-ja.com

Codeigniterセッションがajax呼び出しでバグアウト

私のCodeIgniterアプリはセッションライブラリを使用し、データをDBに保存します。

特定のajax呼び出しの後に空白のセッションが作成されるという問題が発生しています。

調査したところ、セッションの検証を必要とする2つの同時の関数呼び出しが発生したようです。 1つは失敗し、もう1つは問題ありません。

同時に発砲しないようにすることで、これを修正することができました。しかし、私はそれでも失敗する理由を理解していません。 1回の呼び出しでユーザーCookieを更新し、2回目の呼び出しで無効にする必要がありますか?それとも、DBを読んでいるときにどういうわけか死ぬのでしょうか?

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ではうまく機能しません。 1つの解決策は、それがajax呼び出しの場合、セッションの更新を許可しないことです。唯一の問題は、私たちのサイトがほとんどajaxで構築されていることです。

また、sess_time_to_updateを非常に頻繁なものに下げただけで、ajaxは問題なく動作していました。また、ブラウザが更新され、タイムアウトしませんでした。 ajax呼び出し時にセッションIDがすでに変更されており、ブラウザーのCookieが更新されなかった理由がわかりません。

23
lamp_scaler

これを試して

<?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があり、ajax呼び出しが行われる前にsession_idが期限切れになると、その呼び出しは失敗します。

/ application/libraries /にMY_Session(または設定した任意の接頭辞)という名前のphpファイルを作成し、そこにこのコードを貼り付ければ、それだけです。この関数は、セッションクラスのsess_update関数をオーバーライドし、リクエストがajaxによって行われたかどうかをすべてのリクエストでチェックし、sess_update関数をスキップします。

その悪い考えは、sess_expirationをより高い値に設定します。これは、セッションのハイジャックからユーザーを保護するセキュリティ機能です

PD:私は英語があまり上手ではありません。何か理解できない場合は、私に知らせてください。

26
Agustin Baez

安定したブランチにマージされるまで、解決策は(最終的に!)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`)
);

詳細については、 プル1283コメント 上から下を参照してください。

5
Jordan Arseno

この問題が発生しました。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']; 
3

次の設定を使用すると、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」の組み合わせが正常に機能するようになります。これは単純で単純なバグ修正です。

1
user1881928

このスレッドが古いにもかかわらず、まだ使用中の古いCIバージョンがたくさんあるようで、2セントを追加したかったのです。 Code IgniterでのAJAX呼び出しの問題を解決するために数日を費やしました。主な問題をカバーするソリューションがありますが、一部のソリューションは「素晴らしい」ものではありません。CIバージョン私が(まだ)使用しているのは_2.1.3_

私のアプリケーションではAJAX呼び出しが有効なセッションを維持するためにlast_activityフィールドを更新する必要があるため、単純にAJAXでセッションの更新を中止するだけでは不十分です。呼び出します。

Sess_updateとsess_readのエラーチェックはこのCIバージョンでは不十分であり(私は最近のバージョンを調査していません)、多くの問題がそこから始まります。

パート1:sess_update()

複数のAJAX呼び出しは、後の呼び出しのためにデータベースをロックするという結果になる競合状態を作成します。更新クエリを実行しようとしたが、データベースがロックされている場合、エラーが発生し、クエリはfalseを返します、ただしCookieは新しいデータで更新されますか?... BAD!また、すべての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を使用しているサイトでまだ初期セッションタイムアウトが発生している可能性がある人と私の発見を共有する必要があると感じました私のもののように。

0
Squivo

良い解決策はここにあります。 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セッションが失われる2つの理由があるという点を理解します。 1つは、不正なajax呼び出しが行われたときにセッションが更新され、セッションが失われることです。 2番目は、不正なajax呼び出しの後、CIのSESSIONライブラリーのsess_destroy関数に影響を与えます。 SO「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
khalrd

コアCIセッションクラス処理セッションに欠陥があるようです。

魅力のように機能する代替セッションライブラリを見つけました。

CI代替セッションライブラリ

コアCI_Sessionクラスを置き換えるのではなく、拡張することをお勧めします。

拡張するには、MY_Session.phpapplication/librariesファイルを作成します。代替ライブラリの内容を貼り付け、class CI_Sessionclass MY_Session extends CI_Sessionに置き換えます。

_flashdata_mark()_flashdata_sweep()_get_time()_set_cookie()_serialize()からprotectedを削除します、_unserialize()_sess_gc()関数。

それが役に立てば幸い。

0
Zeshan

私はajaxで画像をアップロードするときにまったく同じ問題を抱えていたので、configのsess_expirationを次のように設定しました。

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

そして、それは私の問題を修正しました。

0
georgesamper