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

PHP GD 生成驗證碼完整實作:干擾線、點雜訊、TTF 字體與 Session 驗證

     編輯:PHP 相關知識

PHP 圖形驗證碼生成與驗證最佳實踐

驗證碼是防止機器人自動提交的第一道防線。本文提供生產級驗證碼實作:真彩色畫布、干擾線/點雜訊、字元扭曲、TTF 中文支援、Session 安全儲存與 AJAX 驗證流程。

完整驗證碼生成器 (captcha.php)

<?php
declare(strict_types=1);
session_start();

// 設定參數
$width = 130;
$height = 45;
$codeLength = 5;
$fontSize = 22;
$fontFile = __DIR__ . '/fonts/NotoSansSC-Regular.otf'; // 需下載中文字體
$charset = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz'; // 排除易混淆

// 建立真彩色畫布
$im = imagecreatetruecolor($width, $height);

// 背景色 (淺色系)
$bgColor = imagecolorallocate($im, 245, 245, 250);
imagefill($im, 0, 0, $bgColor);

// 干擾線 (3-5 條)
for ($i = 0, $count = random_int(3, 5); $i < $count; $i++) {
    $color = imagecolorallocate($im, random_int(180, 255), random_int(180, 255), random_int(180, 255));
    imageline($im,
        random_int(0, $width), random_int(0, $height),
        random_int(0, $width), random_int(0, $height),
        $color
    );
}

// 干擾點 (50-100 個)
for ($i = 0, $count = random_int(50, 100); $i < $count; $i++) {
    $color = imagecolorallocate($im, random_int(0, 255), random_int(0, 255), random_int(0, 255));
    imagesetpixel($im, random_int(0, $width), random_int(0, $height), $color);
}

// 干擾弧線 (增加識別難度)
for ($i = 0; $i < 2; $i++) {
    $color = imagecolorallocate($im, random_int(150, 220), random_int(150, 220), random_int(150, 220));
    imagearc($im,
        random_int(-10, $width), random_int(-10, $height),
        random_int(50, $width * 2), random_int(50, $height * 2),
        random_int(0, 360), random_int(0, 360),
        $color
    );
}

// 生成隨機碼
$code = '';
for ($i = 0; $i < $codeLength; $i++) {
    $code .= $charset[random_int(0, strlen($charset) - 1)];
}

// 安全儲存至 Session (雜湊存儲,防洩露)
$_SESSION['captcha_hash'] = password_hash(strtoupper($code), PASSWORD_DEFAULT);
$_SESSION['captcha_time'] = time();

// 繪製文字
$textColor = imagecolorallocate($im, random_int(20, 80), random_int(20, 80), random_int(20, 80));
$x = 12;
$charWidth = ($width - 24) / $codeLength;

for ($i = 0; $i < $codeLength; $i++) {
    $char = $code[$i];
    $y = random_int(28, 36);
    $angle = random_int(-18, 18);
    
    if (file_exists($fontFile) && function_exists('imagettftext')) {
        // TTF 字體 (支援中文、反鋸齒)
        imagettftext($im, $fontSize, $angle, $x, $y, $textColor, $fontFile, $char);
        $x += $charWidth + random_int(-2, 2);
    } else {
        // 內建字體 (備援)
        imagestring($im, 5, $x, random_int(10, 14), $char, $textColor);
        $x += 22;
    }
}

// 輸出
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');
imagepng($im);
imagedestroy($im);

前端整合 (HTML + JS)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>驗證碼測試</title>
    <style>
        .captcha-img { cursor: pointer; vertical-align: middle; border: 1px solid #ddd; }
        .input-group { display: flex; align-items: center; gap: 10px; margin: 10px 0; }
    </style>
</head>
<body>
<form id="test-form">
    <div class="input-group">
        <input type="text" name="captcha" id="captcha-input" placeholder="輸入驗證碼" required>
        <img id="captcha-img" class="captcha-img" src="captcha.php" alt="驗證碼">
        <button type="button" id="refresh-captcha">換一張</button>
    </div>
    <button type="submit">提交驗證</button>
</form>

<script>
document.getElementById('refresh-captcha').addEventListener('click', function () {
    // 加時間戳防快取
    document.getElementById('captcha-img').src = 'captcha.php?t=' + Date.now();
});

document.getElementById('test-form').addEventListener('submit', async function (e) {
    e.preventDefault();
    const code = document.getElementById('captcha-input').value.trim();
    if (!code) return alert('請輸入驗證碼');
    
    const formData = new FormData();
    formData.append('captcha', code);
    
    try {
        const res = await fetch('verify.php', { method: 'POST', body: formData });
        const result = await res.json();
        alert(result.message);
        if (result.success) {
            // 成功後重新整理驗證碼
            document.getElementById('captcha-img').src = 'captcha.php?t=' + Date.now();
            document.getElementById('captcha-input').value = '';
        }
    } catch (err) {
        console.error(err);
        alert('請求失敗');
    }
});
</script>
</body>
</html>

後端驗證 (verify.php)

<?php
declare(strict_types=1);
session_start();

header('Content-Type: application/json; charset=utf-8');

$input = trim($_POST['captcha'] ?? '');
if ($input === '') {
    echo json_encode(['success' => false, 'message' => '請輸入驗證碼']);
    exit;
}

// 取得 Session 中的雜湊
$hash = $_SESSION['captcha_hash'] ?? '';
$time = $_SESSION['captcha_time'] ?? 0;

// 時效檢查 (5 分鐘)
if (time() - $time > 300) {
    echo json_encode(['success' => false, 'message' => '驗證碼已過期,請重新獲取']);
    unset($_SESSION['captcha_hash'], $_SESSION['captcha_time']);
    exit;
}

// 驗證 (不分大小寫)
if ($hash && password_verify(strtoupper($input), $hash)) {
    // 驗證成功:銷毀一次性驗證碼
    unset($_SESSION['captcha_hash'], $_SESSION['captcha_time']);
    echo json_encode(['success' => true, 'message' => '驗證通過!']);
} else {
    echo json_encode(['success' => false, 'message' => '驗證碼錯誤']);
}

進階強化方向

  1. 點擊/滑動驗證:前端 Canvas 互動 + 後端軌跡分析,體驗更好。
  2. 字體隨機化:準備多組 TTF 字體,每次隨機選擇。
  3. 背景圖片:使用隨機背景圖替代純色,增加 OCR 識別難度。
  4. 速率限制:同 IP 限制驗證碼請求頻率 (如 10 次/分鐘)。
  5. 分散式 Session:叢集環境使用 Redis 儲存驗證碼雜湊。

常見問題排查

現象原因解決
圖片顯示破圖輸出前有空白/BOM/echo檢查檔案編碼 (UTF-8 無 BOM)、啟用 output_buffering
imagettftext 無效FreeType 未編譯或字體路徑錯誤phpinfo() 確認 FreeType Support、使用絕對路徑
中文顯示方塊字體不支援中文下載 Noto Sans SC、Source Han Sans 等支援 CJK 的字體
Session 驗證失效Session 未啟動/路徑/域名不匹配確認 session_start()、cookie 設定、跨子域共享
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved