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

CodeIgniter Cookie 登入機制完整實作 (加密/自動續期/多控制器共用)

     編輯:PHP 綜合知識

CodeIgniter 提供 Input::set_cookie()、Input::cookie() 封裝原生 setcookie(),支援加密、HttpOnly、Secure、SameSite 等現代 Cookie 屬性。本文實作一套完整的「記住我」Cookie 登入機制,涵蓋登入驗證、Cookie 寫入/讀取/續期、多控制器基類共用、登出銷毀與安全加固。

一、配置啟用 Cookie 加密 (config.php)

$config['encryption_key'] = 'your_32_char_random_key_here'; // 必填,用於 Cookie 加密
$config['cookie_prefix']   = 'ci_';       // 前綴避免衝突
$config['cookie_domain']   = '';          // 空 = 當前域名;跨子域可設 '.example.com'
$config['cookie_path']     = '/';
$config['cookie_secure']   => TRUE;       // 僅 HTTPS 傳輸 (生產環境必開)
$config['cookie_httponly'] => TRUE;       // 禁止 JS 讀取 (防 XSS 竊取)
$config['cookie_samesite'] => 'Lax';      // CSRF 保護:Lax/Strict/None

二、登入控制器 (Auth.php)

class Auth extends CI_Controller {
    public function login() {
        // 1. 顯示登入頁 (GET)
        if ($this->input->method() === 'get') {
            $this->load->view('auth/login');
            return;
        }

        // 2. 處理表單提交 (POST)
        $username = $this->input->post('username', TRUE);
        $password = $this->input->post('password');
        $remember = (bool) $this->input->post('remember'); // 記住我

        if (empty($username) || empty($password)) {
            $this->session->set_flashdata('error', '帳號密碼不可為空');
            redirect('auth/login');
        }

        // 3. 驗證用戶 (模型)
        $this->load->model('User_model');
        $user = $this->User_model->verify($username, $password);

        if (!$user) {
            $this->session->set_flashdata('error', '帳號或密碼錯誤');
            redirect('auth/login');
        }

        // 4. 寫入 Session (當前請求即時生效)
        $sessionData = [
            'user_id'   => $user->id,
            'username'  => $user->username,
            'role'      => $user->role,
            'login_at'  => time(),
        ];
        $this->session->set_userdata($sessionData);

        // 5. 寫入 Cookie (記住我)
        if ($remember) {
            $cookieData = [
                'user_id'  => $user->id,
                'token'    => $this->_generate_token($user), // 簽名 token
                'expire'   => time() + 30 * 86400,          // 30 天
            ];
            // 加密儲存單一 Cookie 值 (JSON + encrypt)
            $this->input->set_cookie([
                'name'   => 'remember_me',
                'value'  => $this->encryption->encrypt(json_encode($cookieData)),
                'expire' => 30 * 86400, // 秒
                'secure' => TRUE,
                'httponly' => TRUE,
                'samesite' => 'Lax',
            ]);
        }

        redirect('dashboard');
    }

    public function logout() {
        // 清除 Session
        $this->session->sess_destroy();
        // 刪除 Cookie (設定過期時間為過去)
        $this->input->set_cookie('remember_me', '', -3600);
        redirect('auth/login');
    }

    // 產生簽名 Token (防竄改)
    private function _generate_token($user) {
        $payload = $user->id . '|' . $user->password . '|' . $user->salt; // password 已是 hash
        return hash_hmac('sha256', $payload, $this->config->item('encryption_key'));
    }

    // 驗證 Token
    private function _verify_token($user, $token) {
        return hash_equals($this->_generate_token($user), $token); // 時序攻擊安全比較
    }
}

三、基底控制器 (MY_Controller.php) - 自動識別 Cookie 登入

class MY_Controller extends CI_Controller {
    protected $currentUser = NULL;

    public function __construct() {
        parent::__construct();
        $this->_init_auth();
    }

    private function _init_auth() {
        // 1. 優先檢查 Session (已登入狀態)
        if ($this->session->userdata('user_id')) {
            $this->currentUser = (object) [
                'user_id'  => $this->session->userdata('user_id'),
                'username' => $this->session->userdata('username'),
                'role'     => $this->session->userdata('role'),
            ];
            return;
        }

        // 2. 嘗試從 Cookie 自動登入
        $cookieVal = $this->input->cookie('remember_me', TRUE); // TRUE = XSS clean + decrypt
        if ($cookieVal) {
            try {
                $data = json_decode($this->encryption->decrypt($cookieVal), TRUE);
                if ($data && isset($data['user_id'], $data['token'], $data['expire'])) {
                    // 檢查過期
                    if ($data['expire'] > time()) {
                        $this->load->model('User_model');
                        $user = $this->User_model->get_by_id($data['user_id']);
                        if ($user && $this->_verify_token($user, $data['token'])) {
                            // 登入成功:重建 Session、續期 Cookie
                            $this->_regenerate_session($user);
                            $this->_refresh_cookie($user);
                            return;
                        }
                    }
                }
            } catch (Exception $e) {
                log_message('error', 'Cookie 解密失敗: ' . $e->getMessage());
            }
        }

        // 3. 皆失敗:導向登入頁 (可選,視頁面是否需強制登入)
        // $this->_require_login();
    }

    private function _regenerate_session($user) {
        $this->session->sess_regenerate(TRUE); // 產生新 Session ID,防止 Session Fixation
        $this->session->set_userdata([
            'user_id'  => $user->id,
            'username' => $user->username,
            'role'     => $user->role,
            'login_at' => time(),
        ]);
        $this->currentUser = (object) [
            'user_id'  => $user->id,
            'username' => $user->username,
            'role'     => $user->role,
        ];
    }

    private function _refresh_cookie($user) {
        $cookieData = [
            'user_id' => $user->id,
            'token'   => $this->_generate_token($user),
            'expire'  => time() + 30 * 86400,
        ];
        $this->input->set_cookie([
            'name'     => 'remember_me',
            'value'    => $this->encryption->encrypt(json_encode($cookieData)),
            'expire'   => 30 * 86400,
            'secure'   => TRUE,
            'httponly' => TRUE,
            'samesite' => 'Lax',
        ]);
    }

    // 子類可覆寫決定是否強制登入
    protected function _require_login() {
        if (!$this->currentUser) {
            redirect('auth/login?redirect=' . urlencode(current_url()));
        }
    }

    // 共用的 Token 生成/驗證 (Auth 也可繼承或提取到 Trait)
    private function _generate_token($user) { /* 同 Auth::_generate_token */ }
    private function _verify_token($user, $token) { /* 同 Auth::_verify_token */ }
}

四、受保護控制器繼承 MY_Controller

class Dashboard extends MY_Controller {
    public function __construct() {
        parent::__construct();
        $this->_require_login(); // 強制登入
    }

    public function index() {
        $data['user'] = $this->currentUser;
        $this->load->view('dashboard/index', $data);
    }
}

五、User_model 關鍵方法

class User_model extends CI_Model {
    public function verify($username, $password) {
        $this->db->where('username', $username);
        $query = $this->db->get('users', 1);
        $user = $query->row();
        if ($user && password_verify($password, $user->password_hash)) {
            return $user;
        }
        return FALSE;
    }

    public function get_by_id($id) {
        return $this->db->get_where('users', ['id' => $id], 1)->row();
    }
}

六、安全性加固重點

  1. 加密金鑰:encryption_key 必須 32 字元隨機字串,勿硬編碼,建議放環境變數。
  2. Token 簽名:Cookie 值包含 user_id|password_hash|salt 的 HMAC,防止用戶偽造/提權。
  3. HttpOnly + Secure + SameSite:三重保護防 XSS/CSRF/中間人。
  4. Session Regenerate:登入成功、權限變更時呼叫 sess_regenerate(TRUE) 防 Session Fixation。
  5. 定期輪換 Key:制定金鑰輪換策略,舊 Cookie 自動失效。
  6. 記錄審計日誌:登入/登出/失敗嘗試寫入資料表,支援異常檢測。

七、常見問題 FAQ

Cookie 無法跨子域共用
設定 $config['cookie_domain'] = '.example.com'; (前導點),確保所有子域 HTTPS。
本地開發 HTTPS 憑證問題導致 Secure Cookie 不送出
本地用 lvh.me (解析到 127.0.0.1) + mkcert 產生信任憑證,或暫時關閉 cookie_secure (僅開發)。
加密解密失敗
檢查 encryption_key 長度、PHP openssl 擴展、CI encryption 驅動設定 ($config['encryption_driver'] = 'openssl';)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved