萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP Session 實作使用者登入狀態管理完整教學

Session 基本運作原理

  1. session_start():啟動/恢復會話,發送 Set-Cookie: PHPSESSID=...
  2. 伺服器端儲存資料 (預設檔案系統 /tmp/sess_*)
  3. 後續請求攜帶 Cookie,自動載入 $_SESSION 超全域陣列

核心操作

啟動與寫入

<?php
session_start(); // 必須在輸出前呼叫

$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'tom';
$_SESSION['roles'] = ['user', 'editor'];
$_SESSION['last_activity'] = time();
?>

讀取

<?php
session_start();
if (isset($_SESSION['user_id'])) {
    echo "歡迎,{$_SESSION['username']} (ID: {$_SESSION['user_id']})";
}
?>

刪除單一/全部變數

完整登入/登出實作

config.php - 設定與資料庫

<?php
// config.php
declare(strict_types=1);

define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'myapp');
define('DB_USER', 'app_user');
define('DB_PASS', 'secure_password');
define('SESSION_LIFETIME', 1800); // 30 分鐘

// Session 安全設定 (必須在 session_start() 前)
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? '1' : '0');
ini_set('session.use_strict_mode', '1');
ini_set('session.gc_maxlifetime', (string)SESSION_LIFETIME);
// 可選:自定儲存處理器 (Redis/Database)
?>

login.php - 登入處理

<?php
// login.php
require_once 'config.php';
session_start();

$errors = [];
$redirect = $_GET['redirect'] ?? '/dashboard.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    
    if ($username === '' || $password === '') {
        $errors[] = '帳號密碼不可為空';
    } else {
        try {
            $pdo = new PDO(
                "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=utf8mb4",
                DB_USER, DB_PASS,
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
            );
            
            $stmt = $pdo->prepare('SELECT id, username, password_hash, roles FROM users WHERE username = ?');
            $stmt->execute([$username]);
            $user = $stmt->fetch();
            
            if ($user && password_verify($password, $user['password_hash'])) {
                // 登入成功:重新產生 Session ID (防 Session Fixation)
                session_regenerate_id(true);
                
                $_SESSION['user_id'] = $user['id'];
                $_SESSION['username'] = $user['username'];
                $_SESSION['roles'] = json_decode($user['roles'], true) ?? ['user'];
                $_SESSION['login_time'] = time();
                $_SESSION['last_activity'] = time();
                
                // 記住我功能
                if (!empty($_POST['remember'])) {
                    $selector = bin2hex(random_bytes(16));
                    $validator = bin2hex(random_bytes(32));
                    $hash = hash('sha256', $validator);
                    $expires = time() + 30*86400;
                    
                    $pdo->prepare('INSERT INTO remember_tokens (selector, validator_hash, user_id, expires) VALUES (?, ?, ?, ?)')
                        ->execute([$selector, $hash, $user['id'], date('Y-m-d H:i:s', $expires)]);
                    
                    setcookie('remember_me', $selector . ':' . $validator, $expires, '/', '', true, true);
                }
                
                header('Location: ' . $redirect);
                exit;
            }
            $errors[] = '帳號或密碼錯誤';
        } catch (PDOException $e) {
            error_log('Login error: ' . $e->getMessage());
            $errors[] = '系統錯誤,請稍後再試';
        }
    }
}
?>


登入

    

會員登入

    " . htmlspecialchars($e) . ""; ?>



dashboard.php - 需登入頁面

<?php
// dashboard.php
require_once 'config.php';
session_start();

// 檢查登入狀態
if (empty($_SESSION['user_id'])) {
    header('Location: login.php?redirect=' . urlencode($_SERVER['REQUEST_URI']));
    exit;
}

// 檢查 Session 逾時
if (time() - $_SESSION['last_activity'] > SESSION_LIFETIME) {
    session_destroy();
    header('Location: login.php?expired=1');
    exit;
}
$_SESSION['last_activity'] = time();

// 角色授權檢查
function hasRole(string $role): bool {
    return in_array($role, $_SESSION['roles'] ?? [], true);
}
?>


會員中心

    

歡迎,

角色:

登出

管理後台

logout.php - 登出

<?php
// logout.php
require_once 'config.php';
session_start();

// 刪除記住我 Token
if (isset($_COOKIE['remember_me'])) {
    [$selector] = explode(':', $_COOKIE['remember_me'], 2);
    $pdo = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASS);
    $pdo->prepare('DELETE FROM remember_tokens WHERE selector = ?')->execute([$selector]);
    setcookie('remember_me', '', time() - 3600, '/', '', true, true);
}

// 銷毀 Session
$_SESSION = [];
session_destroy();

header('Location: login.php?logged_out=1');
exit;
?>

安全性強化清單

  • ✅ session_regenerate_id(true) 登入後重新產生 ID
  • ✅ session.use_strict_mode=1 拒絕未初始化的 Session ID
  • ✅ cookie_httponly=1 防 XSS 竊取 Session ID
  • ✅ cookie_secure=1 (HTTPS 環境) 只傳輸加密 Cookie
  • ✅ SameSite=Strict/Lax 防 CSRF (PHP 7.3+ session_set_cookie_params())
  • ✅ 閒置逾時自動登出
  • ✅ 重要操作 (改密碼、綁定裝置) 要求重新驗證密碼
  • ✅ Session 儲存用 Redis/Database (叢集環境共享)

記住我功能資料表

自動登入驗證 (每次請求執行)

prepare('SELECT user_id, validator_hash, expires FROM remember_tokens WHERE selector = ?');
        $stmt->execute([$selector]);
        $token = $stmt->fetch();
        
        if ($token && $token['expires'] > date('Y-m-d H:i:s') && 
            hash_equals($token['validator_hash'], hash('sha256', $validator))) {
            
            // 取得使用者資料
            $stmt = $pdo->prepare('SELECT id, username, roles FROM users WHERE id = ?');
            $stmt->execute([$token['user_id']]);
            $user = $stmt->fetch();
            
            if ($user) {
                session_regenerate_id(true);
                $_SESSION['user_id'] = $user['id'];
                $_SESSION['username'] = $user['username'];
                $_SESSION['roles'] = json_decode($user['roles'], true) ?? ['user'];
                $_SESSION['login_time'] = time();
                $_SESSION['last_activity'] = time();
                
                // 旋轉 Token (安全最佳實務)
                $newSelector = bin2hex(random_bytes(16));
                $newValidator = bin2hex(random_bytes(32));
                $newHash = hash('sha256', $newValidator);
                $pdo->prepare('UPDATE remember_tokens SET selector = ?, validator_hash = ? WHERE selector = ?')
                    ->execute([$newSelector, $newHash, $selector]);
                setcookie('remember_me', $newSelector . ':' . $newValidator, 
                    strtotime($token['expires']), '/', '', true, true);
            }
        }
    }
}
copyright © 萬盛學電腦網 all rights reserved