驗證碼是 Web 應用防範機器人攻擊、保護表單安全的第一道防線。PHP 搭配 GD 圖形庫,能輕鬆生成包含干擾線、干擾點、扭曲文字的驗證碼圖片。本文將從環境配置、隨機碼生成策略、圖片繪製核心步驟,到完整可用的類封裝與前端整合,為你呈現一份詳盡的實戰指南。
修改 php.ini,尋找 ;extension=gd 並移除前方分號,重啟 Web 服務器即可。驗證方式:
<?php phpinfo(); ?>
搜尋 "GD Support" 應顯示 enabled,且支援 FreeType、JPEG、PNG 等格式。
<?php
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= $chars[mt_rand(0, strlen($chars) - 1)];
}
?>
<?php
function generateCode($length = 4) {
$code = '';
for ($i = 0; $i < $length; $i++) {
$type = mt_rand(0, 2);
switch ($type) {
case 0: $ascii = mt_rand(48, 57); break; // 0-9
case 1: $ascii = mt_rand(65, 90); break; // A-Z
case 2: $ascii = mt_rand(97, 122); break; // a-z
}
$code .= chr($ascii);
}
return $code;
}
?>
<?php
$pool = 'abcdefghijkmnpqrstuvwxyz23456789'; // 排除易混淆字元
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= $pool[mt_rand(0, strlen($pool) - 1)];
}
?>
<?php
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= dechex(mt_rand(1, 15)); // 1-f
}
?>
安全建議:使用mt_rand()而非rand(),並排除 0/O、1/I/l 等易混淆字元。高安全場景請改用random_int()(PHP 7+)。
<?php
$width = 120; $height = 40;
$img = imagecreatetruecolor($width, $height); // 真彩色畫布
$bgColor = imagecolorallocate($img, 245, 245, 245); // 淺灰背景
$textColor = imagecolorallocate($img, 51, 51, 51); // 深灰文字
$noiseColor = imagecolorallocate($img, 180, 180, 180); // 干擾色
imagefill($img, 0, 0, $bgColor); // 填充背景
?>
<?php
// 干擾線
for ($i = 0; $i < 6; $i++) {
$color = imagecolorallocate($img, mt_rand(150, 255), mt_rand(150, 255), mt_rand(150, 255));
imageline($img, mt_rand(0, $width), mt_rand(0, $height), mt_rand(0, $width), mt_rand(0, $height), $color);
}
// 干擾點
for ($i = 0; $i < 80; $i++) {
$color = imagecolorallocate($img, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
imagesetpixel($img, mt_rand(0, $width), mt_rand(0, $height), $color);
}
?>
<?php
$fontFile = __DIR__ . '/fonts/arial.ttf'; // 需準備 TTF 字體檔
$code = generateCode(4);
$_SESSION['captcha'] = strtoupper($code); // 存入 Session 驗證
for ($i = 0; $i < 4; $i++) {
$x = 15 + $i * 24 + mt_rand(-2, 2);
$y = 28 + mt_rand(-3, 3);
$angle = mt_rand(-15, 15);
$color = imagecolorallocate($img, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
imagettftext($img, 20, $angle, $x, $y, $color, $fontFile, $code[$i]);
}
?>
<?php
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);
?>
<?php
class CaptchaGenerator {
private $width = 130;
private $height = 45;
private $length = 4;
private $fontSize = 22;
private $fontFile;
private $charset = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // 排除易混字元
private $sessionKey = 'captcha_code';
public function __construct(array $options = []) {
foreach ($options as $key => $val) {
if (property_exists($this, $key)) $this->$key = $val;
}
$this->fontFile = $this->fontFile ?? __DIR__ . '/fonts/DejaVuSans-Bold.ttf';
if (!file_exists($this->fontFile)) throw new Exception('字體檔案不存在');
}
public function generate(): string {
$code = '';
for ($i = 0; $i < $this->length; $i++) {
$code .= $this->charset[mt_rand(0, strlen($this->charset) - 1)];
}
$_SESSION[$this->sessionKey] = strtoupper($code);
return $this->renderImage($code);
}
public function verify(string $input): bool {
return isset($_SESSION[$this->sessionKey]) &&
strtoupper($input) === $_SESSION[$this->sessionKey];
}
private function renderImage(string $code): void {
$img = imagecreatetruecolor($this->width, $this->height);
$bg = imagecolorallocate($img, 255, 255, 255);
imagefill($img, 0, 0, $bg);
// 干擾弧線
for ($i = 0; $i < 5; $i++) {
$c = imagecolorallocate($img, mt_rand(180, 255), mt_rand(180, 255), mt_rand(180, 255));
imagearc($img, mt_rand(-10, $this->width), mt_rand(-10, $this->height),
mt_rand(30, 300), mt_rand(30, 200), 55, 44, $c);
}
// 干擾點
for ($i = 0; $i < $this->length * 30; $i++) {
$c = imagecolorallocate($img, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
imagesetpixel($img, mt_rand(0, $this->width), mt_rand(0, $this->height), $c);
}
// 繪文字
for ($i = 0; $i < $this->length; $i++) {
$x = 12 + $i * 28 + mt_rand(-3, 3);
$y = $this->height - 10 + mt_rand(-5, 5);
$angle = mt_rand(-20, 20);
$c = imagecolorallocate($img, mt_rand(0, 80), mt_rand(0, 80), mt_rand(0, 80));
imagettftext($img, $this->fontSize, $angle, $x, $y, $c, $this->fontFile, $code[$i]);
}
header('Content-Type: image/png');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
imagepng($img);
imagedestroy($img);
}
}
// 使用範例
session_start();
$captcha = new CaptchaGenerator(['width' => 140, 'length' => 5]);
$captcha->generate(); // 直接輸出圖片
?>
<form action="verify.php" method="post">
<label>驗證碼:</label>
<input type="text" name="captcha" required />
<img id="captchaImg" src="captcha.php" alt="點擊刷新" style="cursor:pointer;vertical-align:middle" />
<a href="javascript:void(0)" onclick="refreshCaptcha()">看不清?換一張</a>
<button type="submit">提交</button>
</form>
<script>
function refreshCaptcha() {
document.getElementById('captchaImg').src = 'captcha.php?' + Date.now();
}
document.getElementById('captchaImg').addEventListener('click', refreshCaptcha);
</script>
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = trim($_POST['captcha'] ?? '');
if (isset($_SESSION['captcha_code']) && strtoupper($input) === $_SESSION['captcha_code']) {
echo '驗證通過!';
unset($_SESSION['captcha_code']); // 一次性使用
} else {
echo '驗證碼錯誤,請重試。';
}
}
?>
| 優化方向 | 實作建議 |
|---|---|
| 抗 OCR 識別 | 增加波浪扭曲、隨機字體大小/顏色/旋轉、疊加半透明噪點 |
| 效能提升 | 使用 imagecreate() 替代 imagecreatetruecolor()(簡單場景),啟用 OPcache |
| 防重放攻擊 | 驗證成功後立即 unset($_SESSION['captcha']),限制單 Session 最大嘗試次數 |
| 無障礙支援 | 提供音頻驗證碼選項,或整合 hCaptcha/reCAPTCHA v3 |
| 分散式部署 | 使用 Redis 共享 Session,或採用 JWT 簽名驗證碼(無狀態) |
header() 是否提前發送。session_start() 是否在所有輸出前調用、Cookie 域名/路徑設定、跨子域共享設定。imagettftext() 直接繪製字串而非單字元。PHP 生成驗證碼圖片的核心在於:隨機性來源安全、圖形干擾有效性、Session 狀態管理正確。從簡單的腳本到生產級封裝類,關鍵在於細節把控與持續迭代。建議新專案優先考慮成熟的第三方庫(如 gregwar/captcha)或現代無感驗證方案,但在理解原理與客製化需求時,自建方案仍具不可替代的靈活性。