萬盛學電腦網

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

PHP Cookie 自動登入與 MySQL 資料庫驗證登入完整實作

方案一:Cookie 記住帳號自動登入(HMAC 簽名版)

<?php
// config.php
define('AUTH_SECRET', 'your_32_byte_random_secret_key_here'); // 從環境變數讀取最佳
define('AUTH_COOKIE_NAME', 'auth_token');
define('AUTH_COOKIE_LIFETIME', 30 * 86400); // 30 天

// 產生簽名 Token
function createAuthToken(string $username): string {
    $expires = time() + AUTH_COOKIE_LIFETIME;
    $data = "$username|$expires";
    $hash = hash_hmac('sha256', $data, AUTH_SECRET);
    return "$data|$hash";
}

// 驗證 Token
function validateAuthToken(string $token): ?string {
    $parts = explode('|', $token, 3);
    if (count($parts) !== 3) return null;
    
    [$username, $expires, $hash] = $parts;
    
    // 檢查過期
    if ((int)$expires < time()) return null;
    
    // 驗證簽名(防時序攻擊用 hash_equals)
    $expected = hash_hmac('sha256', "$username|$expires", AUTH_SECRET);
    if (!hash_equals($expected, $hash)) return null;
    
    return $username; // 驗證通過,回傳用戶名
}

// 登入處理
function login(string $username, string $password, bool $remember): bool {
    // 實務:查資料庫並 password_verify()
    if ($username === 'demo' && $password === 'demo123') {
        $_SESSION['user_id'] = 1;
        $_SESSION['username'] = $username;
        
        if ($remember) {
            $token = createAuthToken($username);
            setcookie(AUTH_COOKIE_NAME, $token, [
                'expires' => time() + AUTH_COOKIE_LIFETIME,
                'path' => '/',
                'secure' => true,
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
        }
        return true;
    }
    return false;
}

// 自動登入檢查(每頁頂端 include)
function checkAutoLogin(): void {
    if (isset($_SESSION['user_id'])) return; // 已登入
    
    if (empty($_COOKIE[AUTH_COOKIE_NAME])) return;
    
    $username = validateAuthToken($_COOKIE[AUTH_COOKIE_NAME]);
    if ($username) {
        // 實務:$user = $db->findByUsername($username); 檢查狀態
        $_SESSION['user_id'] = 1;
        $_SESSION['username'] = $username;
    } else {
        // Token 無效,清除
        setcookie(AUTH_COOKIE_NAME, '', time() - 3600, '/');
    }
}

// 登出
function logout(): void {
    session_destroy();
    setcookie(AUTH_COOKIE_NAME, '', time() - 3600, '/');
}
?>

方案二:MySQL 資料庫帳密驗證登入(PDO + password_hash)

資料表結構

CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    status TINYINT DEFAULT 1, -- 1=啟用, 0=停用
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

註冊(密碼雜湊)

<?php
// register.php
require 'db.php';

$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$email = trim($_POST['email'] ?? '');

if ($username === '' || $password === '') {
    die('帳號密碼不可為空');
}

if (strlen($password) < 8) {
    die('密碼至少 8 碼');
}

// 檢查帳號是否存在
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
$stmt->execute([$username]);
if ($stmt->fetch()) {
    die('帳號已存在');
}

// 雜湊密碼(預設 BCRYPT,cost=10)
$hash = password_hash($password, PASSWORD_DEFAULT);

$stmt = $pdo->prepare('INSERT INTO users (username, password_hash, email) VALUES (?, ?, ?)');
$stmt->execute([$username, $hash, $email]);

echo '註冊成功,請登入';
?>

登入驗證

<?php
// login.php
session_start();
require 'db.php';
require 'auth.php'; // 上方的 createAuthToken/validateAuthToken

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $remember = isset($_POST['remember']);
    
    $stmt = $pdo->prepare('SELECT id, username, password_hash, status FROM users WHERE username = ?');
    $stmt->execute([$username]);
    $user = $stmt->fetch();
    
    if ($user) {
        if (!$user['status']) {
            $error = '帳號已停用';
        } elseif (password_verify($password, $user['password_hash'])) {
            // 登入成功
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            
            // 更新最後登入時間
            $pdo->prepare('UPDATE users SET last_login = NOW() WHERE id = ?')->execute([$user['id']]);
            
            if ($remember) {
                $token = createAuthToken($user['username']);
                setcookie(AUTH_COOKIE_NAME, $token, [
                    'expires' => time() + AUTH_COOKIE_LIFETIME,
                    'path' => '/',
                    'secure' => true,
                    'httponly' => true,
                    'samesite' => 'Lax',
                ]);
            }
            header('Location: dashboard.php');
            exit;
        } else {
            $error = '帳號或密碼錯誤';
        }
    } else {
        // 統一錯誤訊息,防止用戶枚舉
        $error = '帳號或密碼錯誤';
    }
}
?>
<!DOCTYPE html>
<html><head><title>登入</title></head><body>
<h2>會員登入</h2>
<?php if ($error): ?><p style="color:red"><?= htmlspecialchars($error) ?></p><?php endif; ?>
<form method="post">
    <label>帳號:<input type="text" name="username" required autocomplete="username"></label><br><br>
    <label>密碼:<input type="password" name="password" required autocomplete="current-password"></label><br><br>
    <label><input type="checkbox" name="remember"> 記住我 (30 天)</label><br><br>
    <input type="submit" value="登入">
</form>
</body></html>

受保護頁面範例

<?php
// dashboard.php
session_start();
require 'auth.php';

checkAutoLogin(); // 嘗試自動登入

if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

// 取得用戶資料
require 'db.php';
$stmt = $pdo->prepare('SELECT username, email, created_at FROM users WHERE id = ?');
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
?>
<!DOCTYPE html>
<html><head><title>會員中心</title></head><body>
<h1>歡迎,<?= htmlspecialchars($user['username']) ?></h1>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>
<p>註冊時間: <?= $user['created_at'] ?></p>
<a href="logout.php">登出</a>
</body></html>

登出

<?php
// logout.php
session_start();
require 'auth.php';
logout();
header('Location: login.php');
?>

安全性強化清單

  1. 密碼雜湊:password_hash() + password_verify(),定期 password_needs_rehash() 升級演算法。
  2. HTTPS 全站:Cookie Secure、HSTS、CSP。
  3. CSRF Token:表單、AJAX 請求雙重驗證。
  4. 限流:登入失敗計數、IP 封鎖、reCAPTCHA。
  5. Session 設定:session.cookie_httponly=1、session.use_strict_mode=1、session.cookie_samesite=Lax。
  6. 資料庫最小權限:應用帳號僅擁有必要 CRUD 權限。
  7. 記錄審計日誌:登入成功/失敗、IP、User-Agent、時間。
copyright © 萬盛學電腦網 all rights reserved