圖形驗證碼(CAPTCHA)是防止機器人提交表單的基礎手段。PHP GD 擴充提供繪圖 API,可動態生成干擾線、噪點、扭曲文字。本文提供一個完整、可配置的驗證碼類,並說明安全性與無障礙考量。
<?php
// Captcha.php
class Captcha {
private int $width;
private int $height;
private int $length;
private int $fontSize;
private string $charset;
private string $fontFile; // TTF 字體路徑,支援中文/特殊字型
private array $bgColor;
private array $textColorRange;
private array $noiseColorRange;
public function __construct(array $options = []) {
$this->width = $options['width'] ?? 120;
$this->height = $options['height'] ?? 40;
$this->length = $options['length'] ?? 4;
$this->fontSize = $options['font_size'] ?? 18;
$this->charset = $options['charset'] ?? '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
$this->fontFile = $options['font_file'] ?? '';
$this->bgColor = $options['bg_color'] ?? [255, 255, 255];
$this->textColorRange= $options['text_color'] ?? [[50, 150], [50, 150], [50, 150]];
$this->noiseColorRange= $options['noise_color'] ?? [[180, 230], [180, 230], [180, 230]];
}
public function generate(): string {
$code = '';
for ($i = 0; $i < $this->length; $i++) {
$code .= $this->charset[random_int(0, strlen($this->charset) - 1)];
}
$_SESSION['captcha_code'] = strtoupper($code); // 儲存供驗證
$this->outputImage($code);
return $code;
}
public function verify(string $input): bool {
$stored = $_SESSION['captcha_code'] ?? '';
unset($_SESSION['captcha_code']); // 一次性
return strtoupper(trim($input)) === $stored;
}
private function outputImage(string $code): void {
$im = imagecreatetruecolor($this->width, $this->height);
// 背景
$bg = imagecolorallocate($im, ...$this->bgColor);
imagefill($im, 0, 0, $bg);
// 邊框
$border = imagecolorallocate($im, 200, 200, 200);
imagerectangle($im, 0, 0, $this->width - 1, $this->height - 1, $border);
// 干擾線
for ($i = 0; $i < 5; $i++) {
$color = imagecolorallocate($im,
random_int(...$this->noiseColorRange[0]),
random_int(...$this->noiseColorRange[1]),
random_int(...$this->noiseColorRange[2])
);
imageline($im,
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($im,
random_int(...$this->noiseColorRange[0]),
random_int(...$this->noiseColorRange[1]),
random_int(...$this->noiseColorRange[2])
);
imagesetpixel($im, random_int(0, $this->width-1), random_int(0, $this->height-1), $color);
}
// 文字
$x = 10;
for ($i = 0; $i < $this->length; $i++) {
$char = $code[$i];
$color = imagecolorallocate($im,
random_int(...$this->textColorRange[0]),
random_int(...$this->textColorRange[1]),
random_int(...$this->textColorRange[2])
);
$y = random_int($this->fontSize + 2, $this->height - 4);
$angle = random_int(-15, 15);
if ($this->fontFile && file_exists($this->fontFile)) {
// TTF 字體:支援抗鋸齒、中文、旋轉
imagettftext($im, $this->fontSize, $angle, $x, $y, $color, $this->fontFile, $char);
$box = imagettfbbox($this->fontSize, $angle, $this->fontFile, $char);
$x += $box[2] - $box[0] + 2;
} else {
// 內建字體(僅限 ASCII、無旋轉)
imagestring($im, 5, $x, $y - $this->fontSize, $char, $color);
$x += imagefontwidth(5) + 2;
}
}
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
imagepng($im);
imagedestroy($im);
}
}
// 使用範例:captcha.php
session_start();
require_once 'Captcha.php';
$captcha = new Captcha([
'font_file' => __DIR__ . '/fonts/DejaVuSans.ttf', // 需自備 TTF
'length' => 5,
'width' => 150,
'height' => 50,
]);
$captcha->generate();
<form action="login.php" method="post">
<img id="captcha-img" src="captcha.php" alt="驗證碼" title="點擊更換" style="cursor:pointer;vertical-align:middle">
<input type="text" name="captcha" required autocomplete="off">
<button type="submit">登入</button>
</form>
<script>
document.getElementById('captcha-img').addEventListener('click', function() {
this.src = 'captcha.php?' + Date.now();
});
</script>
<?php
// login.php
session_start();
require_once 'Captcha.php';
$captcha = new Captcha();
if (!$captcha->verify($_POST['captcha'] ?? '')) {
die('驗證碼錯誤');
}
// 繼續登入邏輯...
?>
unset,防重放。| 方案 | 優點 | 缺點 |
|---|---|---|
| Google reCAPTCHA v3 / hCaptcha | 無感驗證、極高安全性、免維護 | 依賴第三方、隱私考量、中國大陸需特殊域名 |
| Turnstile (Cloudflare) | 免費、隱私優先、支援隱私通道 | 相對較新 |
| 自建 GD 驗證碼 | 完全可控、無外部依賴、離線可用 | 需自行對抗 OCR、無障礙需額外開發 |
GD 生成驗證碼適合中小型專案或無法接入外部服務的環境。採用 TTF 字體、隨機干擾、Session 一次性驗證,可達到基本防機器人效果。長期演進建議評估無感驗證方案,提升使用者體驗。