GD 庫是 PHP 內建圖形處理擴展,本文提供生產級驗證碼實作:環境檢查、類別封裝、干擾線/點、Session 儲存、前端整合、無障礙考量與現代替代方案。
# 檢查 GD 支援
php -m | grep gd
# 或
php -r "var_dump(gd_info());"
# Ubuntu/Debian 安裝
apt-get install php-gd
# CentOS/RHEL
yum install php-gd
# 重啟 Web Server
systemctl restart php-fpm nginx
<?php
namespace App\Captcha;
class Captcha {
private $width = 120;
private $height = 40;
private $length = 4;
private $fontSize = 18;
private $charset = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // 排除易混淆字元
private $sessionKey = 'captcha_code';
public function __construct(array $options = []) {
foreach ($options as $k => $v) {
if (property_exists($this, $k)) $this->$k = $v;
}
}
public function generate(): string {
$code = $this->generateCode();
$this->saveToSession($code);
$this->outputImage($code);
return $code;
}
public function verify(string $input): bool {
$stored = $_SESSION[$this->sessionKey] ?? '';
unset($_SESSION[$this->sessionKey]); // 一次性驗證,防重放
return strtoupper($input) === strtoupper($stored);
}
private function generateCode(): string {
$code = '';
$max = strlen($this->charset) - 1;
for ($i = 0; $i < $this->length; $i++) {
$code .= $this->charset[random_int(0, $max)];
}
return $code;
}
private function saveToSession(string $code): void {
if (session_status() === PHP_SESSION_NONE) session_start();
$_SESSION[$this->sessionKey] = $code;
}
private function outputImage(string $code): void {
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
$img = imagecreatetruecolor($this->width, $this->height);
// 背景色
$bgColor = imagecolorallocate($img, 245, 245, 245);
imagefill($img, 0, 0, $bgColor);
// 干擾線
for ($i = 0; $i < 5; $i++) {
$color = imagecolorallocate($img, random_int(180, 255), random_int(180, 255), random_int(180, 255));
imageline($img, random_int(0, $this->width), random_int(0, $this->height), random_int(0, $this->width), random_int(0, $this->height), $color);
}
// 干擾點
for ($i = 0; $i < 50; $i++) {
$color = imagecolorallocate($img, random_int(0, 255), random_int(0, 255), random_int(0, 255));
imagesetpixel($img, random_int(0, $this->width), random_int(0, $this->height), $color);
}
// 繪製文字
$x = 10;
for ($i = 0; $i < $this->length; $i++) {
$color = imagecolorallocate($img, random_int(0, 100), random_int(0, 150), random_int(0, 200));
$angle = random_int(-15, 15);
$y = random_int($this->height / 2, $this->height - 5);
imagettftext($img, $this->fontSize, $angle, $x, $y, $color, __DIR__ . '/fonts/arial.ttf', $code[$i]);
$x += $this->fontSize + 2;
}
imagepng($img);
imagedestroy($img);
}
}
<!-- HTML -->
<form action="/login" method="POST">
<input type="text" name="username" placeholder="帳號" required>
<input type="password" name="password" placeholder="密碼" required>
<div class="captcha-group">
<input type="text" name="captcha" placeholder="驗證碼" required style="width: 80px">
<img id="captchaImg" src="/captcha" alt="驗證碼" style="vertical-align: middle; cursor: pointer" title="點擊更換">
</div>
<button type="submit">登入</button>
</form>
<script>
document.getElementById('captchaImg').addEventListener('click', function() {
this.src = '/captcha?' + Date.now(); // 防快取
});
</script>
<?php
// /captcha 路由
$captcha = new App\Captcha\Captcha([
'width' => 130,
'height' => 45,
'length' => 4,
]);
$captcha->generate(); // 直接輸出圖片
// /login 路由
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$captcha = new App\Captcha\Captcha();
if (!$captcha->verify($_POST['captcha'] ?? '')) {
die('驗證碼錯誤');
}
// 後續登入邏輯...
}
| 措施 | 說明 |
|---|---|
| 音頻驗證碼 | 為視障用戶提供語音播放選項(需 TTS 服務) |
| 限制嘗試次數 | 同一 IP/Session 5 分鐘內失敗 5 次鎖定 15 分鐘 |
| HTTPS 傳輸 | 防止驗證碼圖片被中間人竊取分析 |
| 字體隨機化 | 準備多種 TTF 字體,每次隨機選擇 |
| 背景噪點算法 | 使用柏林噪音、正弦波紋理替代隨機點 |
file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secret&response=$token")GD 生成驗證碼適合內部系統、低流量表單、無外網環境。公開面向用戶的關鍵表單,建議遷移至 reCAPTCHA v3 / hCaptcha / Turnstile 等無感驗證服務,兼顧安全與體驗。