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

PHP MySQL Session 登入系統完整實作:從資料庫驗證到安全會話管理

     編輯:PHP 相關知識

前言

傳統 mysql_* 函數已移除(PHP 7.0),本文提供基於 PDO、密碼雜湊、預處理語句、Session 安全配置、CSRF 防護、記住我功能的現代化登入系統完整實作,可直接作為專案起始模板。

資料庫結構

CREATE TABLE `users` (
    `id`           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `username`     VARCHAR(50)  NOT NULL UNIQUE,
    `email`        VARCHAR(100) NOT NULL UNIQUE,
    `password`     VARCHAR(255) NOT NULL,  -- bcrypt 雜湊
    `remember_token` VARCHAR(64)  DEFAULT NULL, -- 記住我選擇器
    `token_expires`  DATETIME     DEFAULT NULL,
    `created_at`   TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at`   TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心類別:AuthService

<?php
declare(strict_types=1);

class AuthService {
    private PDO $pdo;
    private int $sessionLifetime = 7200; // 2小時

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        $this->configureSession();
    }

    private function configureSession(): void {
        // 僅 HTTPS 傳輸、HttpOnly 防 XSS、SameSite=Lax 防 CSRF
        ini_set('session.cookie_httponly', '1');
        ini_set('session.cookie_secure', '1');
        ini_set('session.cookie_samesite', 'Lax');
        ini_set('session.use_strict_mode', '1'); // 防 Session Fixation
        ini_set('session.gc_maxlifetime', (string)$this->sessionLifetime);
        session_set_cookie_params($this->sessionLifetime, '/', '', true, true);
        session_start();
    }

    /** 註冊 */
    public function register(string $username, string $email, string $password): bool {
        $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
        $stmt = $this->pdo->prepare('INSERT INTO `users` (`username`, `email`, `password`) VALUES (?, ?, ?)');
        try {
            return $stmt->execute([$username, $email, $hash]);
        } catch (PDOException $e) {
            if ($e->getCode() === '23000') {
                throw new DomainException('帳號或信箱已存在');
            }
            throw $e;
        }
    }

    /** 登入 */
    public function login(string $username, string $password, bool $remember = false): bool {
        $stmt = $this->pdo->prepare('SELECT `id`, `password`, `username` FROM `users` WHERE `username` = ? OR `email` = ?');
        $stmt->execute([$username, $username]);
        $user = $stmt->fetch();

        if (!$user || !password_verify($password, $user['password'])) {
            return false;
        }

        // 重新生成 Session ID(防 Session Fixation)
        session_regenerate_id(true);
        $_SESSION['user_id']   = $user['id'];
        $_SESSION['username']  = $user['username'];
        $_SESSION['logged_in'] = true;

        // 記住我:產生 selector:validator,資料庫存 selector + hash(validator)
        if ($remember) {
            $selector = bin2hex(random_bytes(16));
            $validator = bin2hex(random_bytes(32));
            $hash = hash('sha256', $validator);

            $expires = (new DateTime())->modify('+30 days')->format('Y-m-d H:i:s');
            $stmt = $this->pdo->prepare('UPDATE `users` SET `remember_token` = ?, `token_expires` = ? WHERE `id` = ?');
            $stmt->execute([$selector, $expires, $user['id']]);

            // Cookie 存 selector:validator(Base64 編碼)
            $cookieValue = base64_encode($selector . ':' . $validator);
            setcookie('remember_me', $cookieValue, [
                'expires'  => time() + 30 * 86400,
                'path'     => '/',
                'httponly' => true,
                'secure'   => true,
                'samesite' => 'Lax',
            ]);
        }
        return true;
    }

    /** 自動登入(每請求中介層呼叫) */
    public function autoLogin(): bool {
        if (isset($_SESSION['user_id'])) return true; // 已有 Session

        $cookie = $_COOKIE['remember_me'] ?? '';
        if (!$cookie) return false;

        $decoded = base64_decode($cookie, true);
        if (!$decoded || !str_contains($decoded, ':')) return false;

        [$selector, $validator] = explode(':', $decoded, 2);
        $hash = hash('sha256', $validator);

        $stmt = $this->pdo->prepare('SELECT `id`, `username` FROM `users` WHERE `remember_token` = ? AND `token_expires` > NOW()');
        $stmt->execute([$selector]);
        $user = $stmt->fetch();

        if (!$user || !hash_equals($hash, $user['remember_token_hash'] ?? '')) {
            // Token 無效或過期,清除 Cookie
            $this->logout();
            return false;
        }

        // 登入成功,建立新 Session
        session_regenerate_id(true);
        $_SESSION['user_id']   = $user['id'];
        $_SESSION['username']  = $user['username'];
        $_SESSION['logged_in'] = true;

        // 滾動更新 Token(選擇性)
        return true;
    }

    /** 登出 */
    public function logout(): void {
        // 清除資料庫 Token
        if (isset($_SESSION['user_id'])) {
            $stmt = $this->pdo->prepare('UPDATE `users` SET `remember_token` = NULL, `token_expires` = NULL WHERE `id` = ?');
            $stmt->execute([$_SESSION['user_id']]);
        }
        // 銷毀 Session
        $_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();
        // 清除記住我 Cookie
        setcookie('remember_me', '', time() - 3600, '/', '', true, true);
    }

    /** 取得當前用戶 */
    public function user(): ?array {
        if (empty($_SESSION['user_id'])) return null;
        $stmt = $this->pdo->prepare('SELECT `id`, `username`, `email` FROM `users` WHERE `id` = ?');
        $stmt->execute([$_SESSION['user_id']]);
        return $stmt->fetch() ?: null;
    }

    /** 檢查是否登入 */
    public function check(): bool {
        return !empty($_SESSION['logged_in']);
    }
}

使用範例:登入頁面

<?php
// login.php
require 'vendor/autoload.php';
require 'config.php'; // $pdo

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

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $remember = !empty($_POST['remember']);

    if ($auth->login($username, $password, $remember)) {
        header('Location: dashboard.php');
        exit;
    }
    $error = '帳號或密碼錯誤';
}
?>
<!DOCTYPE html>
<html>
<head><title>登入</title></head>
<body>
    <h1>登入</h1>
    <?php if ($error): ?><p style="color:red"><?= htmlspecialchars($error) ?></p><?php endif; ?>
    <form method="post">
        帳號/信箱: <input type="text" name="username" required><br>
        密碼: <input type="password" name="password" required><br>
        <label><input type="checkbox" name="remember"> 記住我 30 天</label><br>
        <button type="submit">登入</button>
    </form>
</body>
</html>

中介層:每請求自動登入檢查

<?php
// middleware/auth.php
require 'config.php';
require 'AuthService.php';

$auth = new AuthService($pdo);
$auth->autoLogin(); // 嘗試恢復 Session 或記住我

// 受保護頁面
if (!$auth->check()) {
    header('Location: login.php');
    exit;
}

$user = $auth->user(); // 當前用戶資料

安全加固清單

項目實作
密碼雜湊password_hash(PASSWORD_BCRYPT, cost=12)
SQL 注入防護PDO 預處理語句 (prepare/execute)
Session Fixationsession_regenerate_id(true) 登入後重新生成 ID
Session 劫持cookie_httponly=1, cookie_secure=1, cookie_samesite=Lax
暴力破解失敗計數 + 延遲/鎖定(Redis 計數器)、驗證碼
記住我 TokenSelector:Validator 模式,資料庫存 hash(validator)
CSRF表單隱藏 csrf_token,Session 驗證 hash_equals
XSS輸出 htmlspecialchars()、CSP 標頭

總結

現代 PHP 登入系統核心在於 PDO + bcrypt + Session 安全配置 + Selector:Validator 記住我。本文提供的 AuthService 類別封裝了註冊、登入、自動登入、登出、用戶查詢完整流程,配合中介層模式即可保護全站受限頁面。直接複製、調整資料庫連線、佈署 HTTPS,即可上線。

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