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

PHP MySQL 用戶認證系統完整實作(Session + 安全最佳實踐)

     編輯:PHP 綜合知識

系統需求與設計

  • 用戶註冊、登入、登出
  • Session 管理登入狀態
  • 密碼安全儲存(bcrypt 雜湊)
  • 防止 SQL 注入、XSS、CSRF、Session Fixation
  • 記錄最後登入時間

資料庫設計

CREATE DATABASE IF NOT EXISTS `user_auth` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `user_auth`;

CREATE TABLE `users` (
    `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(50) NOT NULL UNIQUE,
    `email` VARCHAR(190) NOT NULL UNIQUE,
    `password_hash` VARCHAR(255) NOT NULL,
    `last_login` TIMESTAMP NULL DEFAULT NULL,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `is_active` TINYINT(1) DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心類別:Auth.php

<?php
// Auth.php
class Auth {
    private $pdo;
    private const SESSION_KEY = 'auth_user_id';

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        // 防止 Session Fixation:權限變更時重新生成 ID
        // session_regenerate_id(true); // 登入成功時呼叫
    }

    // 註冊
    public function register(string $username, string $email, string $password): array {
        $username = trim($username);
        $email = trim($email);
        $errors = [];

        if ($username === '') $errors[] = '請輸入用戶名';
        if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) $errors[] = '用戶名 3-20 字元,僅限英數底線';
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Email 格式不正確';
        if (strlen($password) < 8) $errors[] = '密碼至少 8 字元';

        if (!empty($errors)) return ['success' => false, 'errors' => $errors];

        // 檢查重複
        $stmt = $this->pdo->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
        $stmt->execute([$username, $email]);
        if ($stmt->fetch()) {
            return ['success' => false, 'errors' => ['用戶名或 Email 已被註冊']];
        }

        // 建立用戶
        $hash = password_hash($password, PASSWORD_DEFAULT);
        $stmt = $this->pdo->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
        $stmt->execute([$username, $email, $hash]);

        return ['success' => true, 'user_id' => $this->pdo->lastInsertId()];
    }

    // 登入
    public function login(string $username, string $password): array {
        $stmt = $this->pdo->prepare("SELECT id, username, password_hash FROM users WHERE username = ? AND is_active = 1");
        $stmt->execute([$username]);
        $user = $stmt->fetch();

        if (!$user || !password_verify($password, $user['password_hash'])) {
            return ['success' => false, 'error' => '帳號或密碼錯誤'];
        }

        // 更新最後登入時間
        $this->pdo->prepare("UPDATE users SET last_login = NOW() WHERE id = ?")->execute([$user['id']]);

        // 重新生成 Session ID 防 Fixation
        session_regenerate_id(true);
        $_SESSION[self::SESSION_KEY] = $user['id'];
        $_SESSION['auth_username'] = $user['username'];

        return ['success' => true, 'user' => $user];
    }

    // 登出
    public function logout(): void {
        $_SESSION = [];
        if (ini_get("session.use_cookies")) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
        }
        session_destroy();
    }

    // 取得當前用戶
    public function user(): ?array {
        if (empty($_SESSION[self::SESSION_KEY])) return null;
        $stmt = $this->pdo->prepare("SELECT id, username, email, last_login, created_at FROM users WHERE id = ? AND is_active = 1");
        $stmt->execute([$_SESSION[self::SESSION_KEY]]);
        return $stmt->fetch() ?: null;
    }

    // 檢查是否登入
    public function check(): bool {
        return $this->user() !== null;
    }

    // 要求登入(中介層用法)
    public function requireLogin(): void {
        if (!$this->check()) {
            header('Location: login.php?redirect=' . urlencode($_SERVER['REQUEST_URI']));
            exit;
        }
    }
}
?>

使用範例

register.php

<?php
require_once 'config.php';
require_once 'Auth.php';

$auth = new Auth($pdo);
$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = $auth->register($_POST['username'] ?? '', $_POST['email'] ?? '', $_POST['password'] ?? '');
    if ($result['success']) {
        header('Location: login.php?registered=1');
        exit;
    }
    $errors = $result['errors'];
}
?>
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>註冊</title></head><body>
<h2>用戶註冊</h2>
<?php if ($errors): ?><ul style="color:red"><?php foreach($errors as $e) echo "<li>".htmlspecialchars($e)."</li>"; ?></ul><?php endif; ?>
<form method="POST">
    <input type="text" name="username" placeholder="用戶名" required><br>
    <input type="email" name="email" placeholder="Email" required><br>
    <input type="password" name="password" placeholder="密碼 (最少 8 碼)" required><br>
    <button type="submit">註冊</button>
</form>
<p><a href="login.php">已有帳號?去登入</a></p>
</body></html>

login.php

<?php
require_once 'config.php';
require_once 'Auth.php';

$auth = new Auth($pdo);
$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = $auth->login($_POST['username'] ?? '', $_POST['password'] ?? '');
    if ($result['success']) {
        $redirect = $_GET['redirect'] ?? 'dashboard.php';
        header('Location: ' . $redirect);
        exit;
    }
    $error = $result['error'];
}
?>
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>登入</title></head><body>
<h2>用戶登入</h2>
<?php if ($error): ?><p style="color:red"><?= htmlspecialchars($error) ?></p><?php endif; ?>
<form method="POST">
    <input type="text" name="username" placeholder="用戶名" required><br>
    <input type="password" name="password" placeholder="密碼" required><br>
    <button type="submit">登入</button>
</form>
<p><a href="register.php">還沒帳號?立即註冊</a></p>
</body></html>

dashboard.php (受保護頁面)

<?php
require_once 'config.php';
require_once 'Auth.php';

$auth = new Auth($pdo);
$auth->requireLogin();  // 未登入自動導向登入頁
$user = $auth->user();
?>
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>會員中心</title></head><body>
<h2>歡迎,<?= htmlspecialchars($user['username']) ?></h2>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>
<p>最後登入: <?= $user['last_login'] ? htmlspecialchars($user['last_login']) : '首次登入' ?></p>
<p><a href="logout.php">登出</a></p>
</body></html>

logout.php

<?php
require_once 'config.php';
require_once 'Auth.php';
$auth = new Auth($pdo);
$auth->logout();
header('Location: login.php?logged_out=1');
exit;

安全檢查清單

  • ✅ 密碼使用 password_hash() (bcrypt) 儲存
  • ✅ PDO 預處理語句防 SQL 注入
  • ✅ htmlspecialchars() 輸出轉義防 XSS
  • ✅ session_regenerate_id(true) 登入時重新生成 Session ID 防 Session Fixation
  • ✅ Session Cookie 設定 HttpOnly、Secure (HTTPS)、SameSite=Strict/Lax
  • ✅ 登出銷毀 Session 並清除 Cookie
  • ✅ CSRF Token 保護狀態變更操作(登入/登出/註冊建議加上)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved