首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

CodeIgniter Session 核心源碼深度解析

     編輯:PHP 相關知識

CodeIgniter Session 核心源碼深度解析

CodeIgniter (CI) 的 Session **不使用 PHP 原生 $_SESSION**,而是實作 **Cookie-based Session**,可選擇資料庫儲存、支援閃存資料、IP/User-Agent 綁定、定期 GC。本文逐層剖析 CI_Session 核心邏輯。

核心配置屬性

class CI_Session {
    var $sess_encrypt_cookie  = FALSE;  // 加密 Cookie
    var $sess_use_database    = FALSE;  // 寫入資料庫
    var $sess_table_name      = '';     // Session 資料表
    var $sess_expiration      = 7200;   // 過期秒數 (預設 2 小時)
    var $sess_expire_on_close = FALSE;  // 關閉瀏覽器即過期
    var $sess_match_ip        = FALSE;  // 綁定 IP
    var $sess_match_useragent = TRUE;   // 綁定 User-Agent (前 120 字元)
    var $sess_cookie_name     = 'ci_session';
    var $cookie_prefix        = '';
    var $cookie_path          = '/';
    var $cookie_domain        = '';
    var $cookie_secure        = FALSE;  // 僅 HTTPS
    var $sess_time_to_update  = 300;    // Cookie 更新間隔 (5 分鐘)
    var $encryption_key       = '';     // 必填,config.php 設定
    var $flashdata_key        = 'flash';
    var $gc_probability       = 5;      // GC 機率 5%
    var $userdata             = [];     // 當前 Session 資料
    var $CI;                            // CI 超級物件
    var $now;                           // 當前時間戳
}

初始化流程 __construct()

  1. 載入配置覆蓋預設值
  2. 檢查 encryption_key 非空
  3. 載入 string helper、encrypt library (若加密)、database (若用 DB)
  4. 取得當前時間 $this->now
  5. 嘗試讀取現有 Session sess_read(),失敗則建立新 Session sess_create()
  6. 處理閃存標記:_flashdata_sweep() 清除舊閃存,_flashdata_mark() 將新閃存標記為 old
  7. 執行 GC _sess_gc()

核心方法詳解

1. sess_read() - 讀取與驗證 Session

function sess_read() {
    // 1. 從 Cookie 取得資料
    $session = $this->CI->input->cookie($this->sess_cookie_name);
    if ($session === FALSE) return FALSE;

    // 2. 解密或驗證 HMAC
    if ($this->sess_encrypt_cookie) {
        $session = $this->CI->encrypt->decode($session);
    } else {
        $hash = substr($session, -32);           // 後 32 字元為 MD5
        $session = substr($session, 0, -32);
        if ($hash !== md5($session . $this->encryption_key)) {
            $this->sess_destroy(); return FALSE; // 竄改偵測
        }
    }

    // 3. 反序列化
    $session = $this->_unserialize($session);
    if (!is_array($session) || !isset($session['session_id'], $session['ip_address'], $session['user_agent'], $session['last_activity'])) {
        $this->sess_destroy(); return FALSE;
    }

    // 4. 過期檢查
    if (($session['last_activity'] + $this->sess_expiration) < $this->now) {
        $this->sess_destroy(); return FALSE;
    }

    // 5. IP 綁定檢查
    if ($this->sess_match_ip && $session['ip_address'] !== $this->CI->input->ip_address()) {
        $this->sess_destroy(); return FALSE;
    }

    // 6. UA 綁定檢查
    if ($this->sess_match_useragent && trim($session['user_agent']) !== trim(substr($this->CI->input->user_agent(), 0, 120))) {
        $this->sess_destroy(); return FALSE;
    }

    // 7. 資料庫二次驗證 (若啟用)
    if ($this->sess_use_database) {
        $this->CI->db->where('session_id', $session['session_id']);
        if ($this->sess_match_ip) $this->CI->db->where('ip_address', $session['ip_address']);
        if ($this->sess_match_useragent) $this->CI->db->where('user_agent', $session['user_agent']);
        $query = $this->CI->db->get($this->sess_table_name);
        if ($query->num_rows() === 0) { $this->sess_destroy(); return FALSE; }
        // 合併 user_data
        $row = $query->row();
        if ($row->user_data) {
            $custom = $this->_unserialize($row->user_data);
            if (is_array($custom)) $session = array_merge($session, $custom);
        }
    }

    $this->userdata = $session;
    return TRUE;
}

2. sess_create() - 建立新 Session

  • 生成 32 字元隨機字串 + IP → md5(uniqid(...)) 作為 session_id
  • 初始化 userdata 陣列 (id, ip, ua, last_activity, user_data)
  • 若用 DB:INSERT 寫入資料表
  • 寫入 Cookie _set_cookie()

3. sess_update() - 定期更新 (預設 5 分鐘)

  • 檢查 last_activity + sess_time_to_update >= now,未到期直接返回
  • 生成新 session_id (Rotation 防固定)
  • 更新 last_activity
  • 若用 DB:UPDATE session_id 與 last_activity
  • 重寫 Cookie

4. sess_write() - 寫入持久層

  • 檔案模式:直接 _set_cookie() (所有資料在 Cookie)
  • DB 模式:分離 cookie_userdata (核心欄位) 與 custom_userdata (業務資料),後者序列化存 user_data 欄位

5. 閃存機制

// 設定:下一次請求可用,之後自動刪除
set_flashdata('msg', '登入成功');

// 讀取:只能讀取一次
$msg = $this->session->flashdata('msg');

// 保留:延長一個請求週期
keep_flashdata('msg');

// 內部實作:
// 設定時鍵名:flash:new:msg
// _flashdata_mark() 將 new 改為 old
// _flashdata_sweep() 刪除 old
// 下次請求 sess_read() 後再次 mark/sweep 完成單次生命週期

6. 序列化處理 _serialize() / _unserialize()

  • 將反斜線 \ 替換為 {{slash}} 再 serialize(),避免資料庫/ Cookie 轉義衝突
  • 反序列化時還原

資料庫 Schema (啟用 sess_use_database 時)

CREATE TABLE ci_sessions (
    session_id VARCHAR(40) NOT NULL DEFAULT '0',
    ip_address VARCHAR(45) NOT NULL DEFAULT '0',
    user_agent VARCHAR(120) NOT NULL,
    last_activity INT(10) UNSIGNED NOT NULL DEFAULT 0,
    user_data TEXT,
    PRIMARY KEY (session_id),
    KEY last_activity_idx (last_activity)
);

常見問題與最佳實踐

問題原因解決
Session 異常中斷IP 變動 (行動網路)、UA 變動、Cookie 大小超過 4KB關閉 sess_match_ip、縮減資料、改用 DB 儲存
閃存讀不到同請求多次讀取、重導向未保留使用 keep_flashdata()、檢查重導向是否經過 Session 初始化
效能瓶頸高流量下 DB 讀寫頻繁、GC 鎖表改用 Redis/Memcached、調大 gc_probability 分母
加密金鑰遺失更換 encryption_key 導致所有 Session 失效部署時同步金鑰、滾動更新策略

進階:整合 Redis 儲存

// application/libraries/MY_Session.php
class MY_Session extends CI_Session {
    protected function _sess_write() {
        // 略過父類 DB 邏輯,改寫 Redis
        $key = $this->sess_cookie_name . ':' . $this->userdata['session_id'];
        $this->CI->redis->setex($key, $this->sess_expiration, serialize($this->userdata));
    }
    // 同樣重寫 _sess_read, _sess_destroy, _sess_gc
}

理解 CI Session 的 Cookie-based 架構、驗證鏈、閃存週期與序列化細節,能快速排查 Session 異常,並為分散式架構遷移奠定基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved