驗證碼是防機器人基本防線。本文提供兩種 GD 實作:簡易版與進階版,並涵蓋 Session 驗證、無障礙、安全性考量。
<?php
if (!extension_loaded('gd')) {
die('GD 未安裝:請安裝 php-gd 並重啟 PHP-FPM/Apache');
}
$gdInfo = gd_info();
echo "GD 版本: {$gdInfo['GD Version']}\n";
echo "FreeType: " . ($gdInfo['FreeType Support'] ? '是' : '否') . "\n";
echo "JPEG: " . ($gdInfo['JPEG Support'] ? '是' : '否') . "\n";
echo "PNG: " . ($gdInfo['PNG Support'] ? '是' : '否') . "\n";
<?php
session_start();
function generateCaptcha(int $length = 4, int $width = 120, int $height = 40): void
{
// 字元集:排除易混淆字元 0/O, 1/I/l
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $chars[random_int(0, strlen($chars) - 1)];
}
$_SESSION['captcha_code'] = $code;
// 建立畫布
$img = imagecreatetruecolor($width, $height);
// 顏色分配
$bgColor = imagecolorallocate($img, 245, 245, 245);
$borderColor = imagecolorallocate($img, 180, 180, 180);
$textColor = imagecolorallocate($img, random_int(0, 100), random_int(0, 80), random_int(0, 80));
$noiseColor = imagecolorallocate($img, 200, 200, 200);
// 背景與邊框
imagefilledrectangle($img, 0, 0, $width, $height, $bgColor);
imagerectangle($img, 0, 0, $width - 1, $height - 1, $borderColor);
// 干擾線
for ($i = 0; $i < 5; $i++) {
$color = imagecolorallocate($img, random_int(150, 255), random_int(150, 255), random_int(150, 255));
imageline($img, random_int(0, $width), random_int(0, $height), random_int(0, $width), random_int(0, $height), $color);
}
// 干擾點
for ($i = 0; $i < 30; $i++) {
$color = imagecolorallocate($img, random_int(100, 255), random_int(100, 255), random_int(100, 255));
imagesetpixel($img, random_int(0, $width), random_int(0, $height), $color);
}
// 繪製文字(需 FreeType 支援中文/TTF)
$fontSize = $height * 0.6;
$x = 10;
$y = $height - ($height - $fontSize) / 2;
if ($gdInfo['FreeType Support'] && file_exists(__DIR__ . '/fonts/DejaVuSans.ttf')) {
imagettftext($img, $fontSize, 0, $x, $y, $textColor, __DIR__ . '/fonts/DejaVuSans.ttf', $code);
} else {
// 內建字體備援(僅支援拉丁字母、固定大小)
imagestring($img, 5, $x, $y - 8, $code, $textColor);
}
// 輸出
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
imagepng($img);
imagedestroy($img);
}
generateCaptcha();
<?php
session_start();
function generateAdvancedCaptcha(int $length = 5): void
{
$chars = '346789ABCDEFGHKLMNPRTUVWXY';
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $chars[random_int(0, strlen($chars) - 1)];
}
$_SESSION['captcha_code'] = strtoupper($code); // 不分大小寫驗證
$width = 160;
$height = 50;
$img = imagecreatetruecolor($width, $height);
// 漸層背景
for ($y = 0; $y < $height; $y++) {
$r = 230 + (int)($y * 20 / $height);
$g = 240 + (int)($y * 15 / $height);
$b = 250;
$color = imagecolorallocate($img, $r, $g, $b);
imageline($img, 0, $y, $width, $y, $color);
}
// 正弦波干擾線
for ($i = 0; $i < 3; $i++) {
$color = imagecolorallocate($img, random_int(100, 200), random_int(100, 200), random_int(100, 200));
$amplitude = random_int(5, 15);
$period = random_int(30, 60);
$phase = random_int(0, $width);
for ($x = 0; $x < $width; $x++) {
$y = $height / 2 + $amplitude * sin(($x + $phase) * 2 * M_PI / $period);
imagesetpixel($img, $x, (int)$y, $color);
}
}
// 字元逐個繪製,隨機旋轉、位移
$fontFile = __DIR__ . '/fonts/DejaVuSans-Bold.ttf';
$fontSize = 24;
$x = 15;
for ($i = 0; $i < $length; $i++) {
$angle = random_int(-15, 15);
$y = random_int(30, 40);
$color = imagecolorallocate($img, random_int(0, 80), random_int(0, 80), random_int(0, 80));
imagettftext($img, $fontSize, $angle, $x, $y, $color, $fontFile, $code[$i]);
$x += $fontSize + random_int(2, 8);
}
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate');
imagepng($img);
imagedestroy($img);
}
<!-- HTML -->
<img id="captcha" src="captcha.php" alt="驗證碼" title="點擊更換" style="cursor:pointer;vertical-align:middle">
<input type="text" name="captcha_input" maxlength="6" required>
<script>
document.getElementById('captcha').onclick = function() {
this.src = 'captcha.php?' + Date.now(); // 防快取
};
</script>
<?php
session_start();
function verifyCaptcha(string $input): bool
{
if (empty($_SESSION['captcha_code'])) return false;
$valid = strtoupper(trim($input)) === $_SESSION['captcha_code'];
// 一次性驗證,防止重放
unset($_SESSION['captcha_code']);
return $valid;
}
// 使用
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verifyCaptcha($_POST['captcha_input'] ?? '')) {
die('驗證碼錯誤');
}
// 正常業務邏輯...
}
| 方案 | 優點 | 缺點 |
|---|---|---|
| Google reCAPTCHA v3 | 無感驗證、極強安全性 | 需網路、隱私考量、中國大陸需備案 |
| hCaptcha | 隱私友善、支援中國 | 需網路、體驗稍差 |
| Turnstile (Cloudflare) | 免費、無感、隱私優先 | 較新、相容性待驗證 |
| 滑動/點選驗證 | 體驗好、移動端友善 | 需前端 SDK、維護成本高 |
| 時間蜜罐 | 零干擾、簡單 | 只擋低級爬蟲 |
random_int() 而非 mt_rand()(加密級隨機)httponly、secure、samesite=StrictCache-Control: no-store