Сессия CodeIgniter не работает только в Китае

Хорошо, я потратил 3 дня, пытаясь выяснить, что здесь происходит, и я в тупике.

Сайт построен с использованием CI. ( http://horizoneslchina.com/)

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

Я расширил базовый класс Input следующим:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 
class MY_Input extends CI_Input {

    function _clean_input_keys($str)
    {
        $config=&get_config('config');
        if(!preg_match("/^[".$config['permitted_uri_chars']."]+$/i",rawurlencode($str)))
        {
            exit('Disallowed Key Characters. '.$str);
        }

        //CleanUTF-8ifsupported
        if(UTF8_ENABLED===TRUE)
        {
            $str=$this->uni->clean_string($str);
        }

        return $str;

    }


}
/* End of file My_Input.php */

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

Этого не происходит в Австралии и не происходит в Индии. Единственное отличие, которое я смог найти, это то, что в китайских системах запятая ставится перед именами сеансов. Например,_ci_session и вышеприведенная функция отбрасывали это как проблему раньше.

У кого-нибудь есть идеи по этому поводу?

3 ответа

I also experienced alot of issues on CI sessions and ended up using this library for sessions ever since i am a happy man 

    <?php
/**
 The EckoTools Session Library

 @package The EckoTools Session Library
 @category Libraries
 @author Hartmut König (h.koenig@eckotools.com)
 @link http://www.okidoe.de
 @version 1.0.2
 @copyright Hartmut König 2009

 A class to handle sessions by using a mySQL database for session related
 data storage providing better security then the default session handler
 used by PHP with added protection against Session Hijacking & Fixation
 including the flashdata-Feature of CI. It don't use Browser or IP to identify
 the user. Instead I generate a fingerprint of different seldom changing data
 (@link _generate_fingerprint)

 To prevent session hijacking, don't forget to use the {@link regenerate_id}
 method whenever you do a privilege change in your application

  --
  -- MYSQL: Table structure for table `ci_sessions`
  --

CREATE TABLE `ci_sessions` (
  `session_id` varchar(32) NOT NULL default '',
  `fingerprint` varchar(32) NOT NULL default '',
  `session_data` blob NOT NULL,
  `session_expire` int(11) NOT NULL default '0',
  PRIMARY KEY  (`session_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


  This class is an adaptation between the original CI Sessions, Native Sessions
  and my own coding

*/

error_reporting(E_ALL);

class CI_Session
{

  /**
   *  Constructor of class
   *
   *  Initializes the class and starts a new session
   *
   *  There is no need to call start_session() after instantiating this class
   *
   *  $gc_maxlifetime    (optional) the number of seconds after which data will be seen as 'garbage' and
   *                     cleaned up on the next run of the gc (garbage collection) routine
   *
   *                     Default is specified in php.ini file
   *
   *  $gc_probability    (optional) used in conjunction with gc_divisor, is used to manage probability that
   *                     the gc routine is started. the probability is expressed by the formula
   *
   *                     probability = $gc_probability / $gc_divisor
   *
   *                     So if $gc_probability is 1 and $gc_divisor is 100 means that there is
   *                     a 1% chance the the gc routine will be called on each request
   *
   *                     Default is specified in php.ini file
   *
   *  $gc_divisor        (optional) used in conjunction with gc_probability, is used to manage probability
   *                     that the gc routine is started. the probability is expressed by the formula
   *
   *                     probability = $gc_probability / $gc_divisor
   *
   *                     So if $gc_probability is 1 and $gc_divisor is 100 means that there is
   *                     a 1% chance the the gc routine will be called on each request
   *
   *                     Default is specified in php.ini file
   *
   *  $security_code     (optional) the value of this argument is appended to the fingerprint before
   *                     creating the md5 hash out of it. this way we'll try to prevent fingerprint
   *                     spoofing
   *
   *                     Default is 'LeouOeEkKpvSnD-YCHd5ogt3y'
   *
   *  $table_name        (optional) You can change the name of that table by setting this property
   *
   *                  Default is 'ci_sessions'
   *
   *  @return void
   */
  function CI_Session(    $security_code="LeouOeEkKpvSnD-YCHd5ogt3y",$table_name="ci_sessions" )
  {
           //-- CI Config
           $this->CI                         = & get_instance();
        $this->flashdata_key     = 'flash'; // prefix for "flash" variables (eg. flash:new:message)
           $table_name                     = $this->CI->config->item('sess_table_name');
            $gc_maxlifetime                = $this->CI->config->item('sess_expiration');
            $gc_probability             = $this->CI->config->item('sess_gc_probability');
            $gc_divisor                        = $this->CI->config->item('sess_gc_divisor');
            $sess_name                        = $this->CI->config->item('sess_cookie_name');

      // if $gc_maxlifetime is specified and is an integer number
      (!empty($gc_maxlifetime) && is_integer($gc_maxlifetime))
                ? @ini_set('session.gc_maxlifetime', $gc_maxlifetime)
                : false;

      // if $gc_probability is specified and is an integer number
      (!empty($gc_probability) && is_integer($gc_probability))
                ? @ini_set('session.gc_probability', $gc_probability)
                : false;

      // if $gc_divisor is specified and is an integer number
      (!empty($gc_divisor) && is_integer($gc_divisor))
                ? @ini_set('session.gc_divisor', $gc_divisor)
                : false;

      (!empty($sess_name))
                ? @ini_set('session.name', $sess_name)
                : false;

      // get session lifetime
      $this->sessionLifetime = ini_get("session.gc_maxlifetime");

      // we'll use this later in order to prevent fingerprint spoofing
      $this->securityCode = $security_code;
      $this->tableName         = $table_name;

      // register the new handler
      session_set_save_handler(
          array(&$this, '_open'),
          array(&$this, '_close'),
          array(&$this, '_read'),
          array(&$this, '_write'),
          array(&$this, '_destroy'),
          array(&$this, '_gc')
            );
      register_shutdown_function('session_write_close');

      // start the session
      session_start();

            // Delete 'old' flashdata (from last request)
           $this->_flashdata_sweep();

            // Mark all new flashdata as old (data will be deleted before next request)
           $this->_flashdata_mark();
  }
    /**
    * Reads given session attribute value
    *
    * @return integer sessionvalue
    */
    function userdata($item)
    {
            //added for backward-compatibility
        if($item == 'session_id')
            {
          return session_id();
            }

            if(isset($_SESSION[$item]))
                {
                return($_SESSION[$item]);
            }

            return(false);
    }
    /**
     * Fetch all session data
     *
     * @access    public
     * @return    mixed
     */
    function all_userdata()
    {
        return ( ! isset($_SESSION)) ? FALSE : $_SESSION;
    }

    /**
    * Sets session attributes to the given values
    *
    * @return void
    */
    function set_userdata($newdata = array(), $newval = '')
    {
        (is_string($newdata))
            ? $newdata = array($newdata => $newval)
            : false;

        if(count($newdata) > 0)
            {
          foreach($newdata as $key => $val)
            {
            $_SESSION[$key] = $val;
            }
            }
    }
    /**
    * Erases given session attributes
    *
    * @return void
    */
    function unset_userdata($newdata = array())
    {
        (is_string($newdata))
            ? $newdata = array($newdata => '')
            : false;

        if(count($newdata) > 0)
            {
          foreach ($newdata as $key => $val)
            {
            unset($_SESSION[$key]);
            }
            }
    }
  /**
   *  Deletes all data related to the session
   *  @return void
   */
  function sess_destroy()
  {
      $this->regenerate_id();
      session_unset();
      session_destroy();
  }

  /**
   *  Regenerates the session id.
   *
   *  <b>Call this method whenever you do a privilege change!</b>
   *
   *  @return void
   */
  function regenerate_id()
  {
      // saves the old session's id
      $oldSessionID = session_id();

      // regenerates the id
      // this function will create a new session, with a new id and containing the data from the old session
      // but will not delete the old session
      session_regenerate_id();

      // because the session_regenerate_id() function does not delete the old session,
      // we have to delete it manually
      $this->_destroy($oldSessionID);
  }
    /**
     * Add or change flashdata, only available
     * until the next request
     *
     * @access    public
     * @param    mixed
     * @param    string
     * @return    void
     */
    function set_flashdata($newdata = array(), $newval = '')
    {
        if (is_string($newdata))
        {
            $newdata = array($newdata => $newval);
        }

        if (count($newdata) > 0)
        {
            foreach ($newdata as $key => $val)
            {
                $flashdata_key = $this->flashdata_key.':new:'.$key;
                $this->set_userdata($flashdata_key, $val);
            }
        }
    }

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

    /**
     * Keeps existing flashdata available to next request.
     *
     * @access    public
     * @param    string
     * @return    void
     */
    function keep_flashdata($key)
    {
        // 'old' flashdata gets removed.  Here we mark all
        // flashdata as 'new' to preserve it from _flashdata_sweep()
        // Note the function will return FALSE if the $key
        // provided cannot be found
        $old_flashdata_key = $this->flashdata_key.':old:'.$key;
        $value = $this->userdata($old_flashdata_key);

        $new_flashdata_key = $this->flashdata_key.':new:'.$key;
        $this->set_userdata($new_flashdata_key, $value);
    }

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

    /**
     * Fetch a specific flashdata item from the session array
     *
     * @access    public
     * @param    string
     * @return    string
     */
    function flashdata($key)
    {
        $flashdata_key = $this->flashdata_key.':old:'.$key;
        return $this->userdata($flashdata_key);
    }

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

    /**
     * Identifies flashdata as 'old' for removal
     * when _flashdata_sweep() runs.
     *
     * @access    private
     * @return    void
     */
    function _flashdata_mark()
    {
        $userdata = $this->all_userdata();
        foreach ($userdata as $name => $value)
        {
            $parts = explode(':new:', $name);
            if (is_array($parts) && count($parts) === 2)
            {
                $new_name = $this->flashdata_key.':old:'.$parts[1];
                $this->set_userdata($new_name, $value);
                $this->unset_userdata($name);
            }
        }
    }

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

    /**
     * Removes all flashdata marked as 'old'
     *
     * @access    private
     * @return    void
     */

    function _flashdata_sweep()
    {
        $userdata = $this->all_userdata();
        foreach($userdata as $key => $value)
            {
            if (strpos($key, ':old:'))
                {
                $this->unset_userdata($key);
                }
            }
    }
  /**
   *  Get the number of online users
   *
   *  @return integer     number of users currently online
   */
  function get_users_online()
  {
      // counts the rows from the database
      $query     = $this->CI->db->query("SELECT COUNT(session_id) as count FROM ".$this->tableName);
      $result = $query->row();

      // return the number of found rows
      return $result->count;
  }

    /**
     * Generates key as protection against Session Hijacking & Fixation.
     * @access private
     * @return string
     */
    function _generate_fingerprint()
    {
        //-- We don't use the ip-adress, because this is a subject to change in most cases (proxies etc.)
        $list = array('HTTP_ACCEPT_CHARSET',
                                    'HTTP_ACCEPT_ENCODING',
                                    'HTTP_ACCEPT_LANGUAGE',
                                    'HTTP_USER_AGENT');
        $key = array($this->securityCode);
        foreach($list as $item)
            {
            $key[] = $this->CI->input->server($item);
            }
        return md5(implode("\0", $key));
    }
  /**
   *  Custom open() function
   *
   *  @access private
   */
  function _open($save_path, $session_name)
  {
      return(true);
  }

  /**
   *  Custom close() function
   *
   *  @access private
   */
  function _close()
  {
      return(true);
  }

  /**
   *  Custom read() function
   *
   *  @access private
   */
  function _read($session_id)
  {
      // reads session data associated with the session id
      // but only
      // - if the fingerprint is the same as the one who had previously written to this session AND
      // - if session has not expired
      $result = $this->CI->db->query("SELECT session_data ".
                                                                      "FROM ".$this->tableName." ".
                                                                      "WHERE session_id = ".$this->CI->db->escape($session_id)." ".
                                                                      "AND fingerprint = ".$this->CI->db->escape($this->_generate_fingerprint())." ".
                                                                      "AND session_expire > '".time()."' LIMIT 1");

      // if anything was found
      if($result->num_rows() > 0)
          {
        // return found data
        $fields = $result->row();

        // Unserialization - PHP handles this automatically
        return $fields->session_data;
          }

      // if there was an error return an empty string - this HAS to be an empty string
      return("");

  }

  /**
   *  Custom write() function
   *
   *  @access private
   */
  function _write($session_id, $session_data)
  {
      // insert OR update session's data - this is how it works:
      // first it tries to insert a new row in the database BUT if session_id is already in the database then just
      // update session_data and session_expire for that specific session_id
      // read more here http://dev.mysql.com/doc/refman/4.1/en/insert-on-duplicate.html
      $result = $this->CI->db->query(
                                                              "INSERT INTO ".$this->tableName." (".
                                                        "session_id,".
                                                        "fingerprint,".
                                                        "session_data,".
                                                        "session_expire".
                                                    ") VALUES (".
                                                              $this->CI->db->escape($session_id).",".
                                                              $this->CI->db->escape($this->_generate_fingerprint()).",".
                                                              $this->CI->db->escape($session_data).",".
                                                              $this->CI->db->escape(time() + $this->sessionLifetime).
                                                                  ")".
                                                          "ON DUPLICATE KEY UPDATE ".
                                                                  "session_data = ".$this->CI->db->escape($session_data).",".
                                                              "session_expire = ".$this->CI->db->escape(time() + $this->sessionLifetime));

            // note that after this type of queries, mysql_affected_rows() returns
            // - 1 if the row was inserted
            // - 2 if the row was updated
            switch($this->CI->db->affected_rows())
                {
          // if the row was inserted
          case 1:
                    return("");
        break;
                // if the row was updated
                case 2:
              return(true);
                break;
                // if something went wrong, return false
                default:
              return(false);
          break;
          }
  }

  /**
   *  Custom destroy() function
   *
   *  @access private
   */
  function _destroy($session_id)
  {
      // deletes the current session id from the database
      $result = $this->CI->db->query("DELETE FROM ".$this->tableName." ".
                                                                  "WHERE session_id = ".$this->CI->db->escape($session_id));

      // if anything happened
      if($this->CI->db->affected_rows())
          {
        return(true);
                }

      // if something went wrong, return false
      return(false);
  }

  /**
   *  Custom gc() function (garbage collector)
   *
   *  @access private
   */
function _gc($maxlifetime)
  {
        // it deletes expired sessions from database
    $result = $this->CI->db->query("DELETE FROM ".$this->tableName." ".
                                                                    "WHERE session_expire < ".$this->CI->db->escape(time() - $maxlifetime));
    }

}
?>

Хотя существует большое количество возможных причин (сеансы CodeIgniter печально известны своей сложностью), это, скорее всего, проблема с часовым поясом, а не проблема характера.

Чтобы устранить проблему - при условии, что вам не нужен малый тайм-аут - увеличьте тайм-аут сеанса!

Каждая страна запрещает некоторые серверы за пределами страны для национальной безопасности. Проверьте, заблокированы ли ваши серверы или нет.

Другие вопросы по тегам