驗證碼(CAPTCHA)是區分人機的關鍵防線。本文從 PHP GD 圖像處理基礎入手,逐步構建完整的動態驗證碼系統:圖像生成、干擾元素、Session 驗證、AJAX 刷新、前端整合,提供可直接部署的生產級代碼。
header('Content-Type: image/png') — 設定輸出 MIMEimagecreatetruecolor($w, $h) — 建立真彩色畫布imagecolorallocate($im, $r, $g, $b) — 分配顏色imagefill($im, 0, 0, $color) — 填充背景imageline、imagestring、imagettftext(TTF)imagepng($im) — 輸出 PNG 二進制流imagedestroy($im) — 釋放記憶體<?php
/**
* Captcha 類:支援配置寬高、字元數、字體、干擾線、噪點、邊框
*/
class Captcha
{
private $width = 120;
private $height = 40;
private $length = 4;
private $fontSize = 20;
private $fontFile = ''; // TTF 字體路徑,空則用內建字體
private $charset = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ'; // 排除易混淆字元
private $useNoise = true;
private $useLines = true;
private $useBorder = false;
private $sessionKey = 'captcha_code';
public function __construct(array $config = [])
{
foreach ($config as $k => $v) {
if (property_exists($this, $k)) $this->$k = $v;
}
}
public function generate(): string
{
$code = $this->generateCode();
$_SESSION[$this->sessionKey] = strtolower($code); // 不區分大小寫驗證
$im = imagecreatetruecolor($this->width, $this->height);
// 背景色(淺色)
$bg = imagecolorallocate($im, mt_rand(220, 255), mt_rand(220, 255), mt_rand(220, 255));
imagefill($im, 0, 0, $bg);
// 干擾線
if ($this->useLines) {
for ($i = 0; $i < 6; $i++) {
$color = imagecolorallocate($im, mt_rand(50, 180), mt_rand(50, 180), mt_rand(50, 180));
imageline($im, mt_rand(0, $this->width), mt_rand(0, $this->height), mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
}
}
// 噪點
if ($this->useNoise) {
for ($i = 0; $i < 80; $i++) {
$color = imagecolorallocate($im, mt_rand(150, 255), mt_rand(150, 255), mt_rand(150, 255));
imagesetpixel($im, mt_rand(0, $this->width), mt_rand(0, $this->height), $color);
}
}
// 繪製驗證碼字元
$x = 10;
for ($i = 0; $i < $this->length; $i++) {
$char = $code[$i];
$color = imagecolorallocate($im, mt_rand(0, 80), mt_rand(0, 80), mt_rand(0, 80));
$angle = mt_rand(-15, 15);
$y = mt_rand($this->height / 3, $this->height * 2 / 3);
if ($this->fontFile && file_exists($this->fontFile)) {
imagettftext($im, $this->fontSize, $angle, $x, $y, $color, $this->fontFile, $char);
$x += $this->fontSize + 2;
} else {
imagestring($im, 5, $x, $y, $char, $color);
$x += imagefontwidth(5) + 2;
}
}
// 邊框
if ($this->useBorder) {
$borderColor = imagecolorallocate($im, 0, 0, 0);
imagerectangle($im, 0, 0, $this->width - 1, $this->height - 1, $borderColor);
}
// 輸出
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);
return $code;
}
private function generateCode(): string
{
$code = '';
$len = strlen($this->charset);
for ($i = 0; $i < $this->length; $i++) {
$code .= $this->charset[mt_rand(0, $len - 1)];
}
return $code;
}
public static function verify(string $input, string $sessionKey = 'captcha_code'): bool
{
if (empty($_SESSION[$sessionKey])) return false;
$valid = strtolower($input) === $_SESSION[$sessionKey];
// 驗證後可選擇銷毀,防止重放
// unset($_SESSION[$sessionKey]);
return $valid;
}
}
<?php
session_start();
require 'Captcha.php';
$captcha = new Captcha([
'width' => 130,
'height' => 45,
'length' => 5,
'fontFile' => __DIR__ . '/fonts/arial.ttf', // 可選
'useNoise' => true,
'useLines' => true,
]);
$captcha->generate();
<?php session_start(); ?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登入</title>
<style>
.captcha-img { cursor: pointer; vertical-align: middle; border: 1px solid #ddd; }
.captcha-input { width: 100px; }
</style>
</head>
<body>
<form method="POST" action="verify.php">
<div>
<label>帳號:<input name="username" required></label>
</div>
<div>
<label>密碼:<input name="password" type="password" required></label>
</div>
<div>
<label>驗證碼:
<input name="captcha" class="captcha-input" required maxlength="5">
<img class="captcha-img" id="captchaImg" src="captcha.php" alt="點擊刷新" title="點擊刷新">
</label>
</div>
<button type="submit">登入</button>
</form>
<script>
document.getElementById('captchaImg').onclick = function() {
this.src = 'captcha.php?' + Date.now(); // 時間戳防快取
};
</script>
</body>
</html>
<?php
session_start();
require 'Captcha.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$captchaInput = $_POST['captcha'] ?? '';
if (!Captcha::verify($captchaInput)) {
die(json_encode(['success' => false, 'msg' => '驗證碼錯誤']));
}
// 驗證通過,進行業務邏輯
$username = $_POST['username'] ?? '';
// ... 登入邏輯
echo json_encode(['success' => true, 'msg' => '登入成功']);
}
| 方向 | 實現方式 |
|---|---|
| TTF 字體反辨識 | 使用變形、扭曲、隨機角度的 imagettftext,載入多種手寫體字體 |
| 中文驗證碼 | 字元集改為常用漢字,imagettftext 渲染,需支援中文字體 |
| 點選/滑塊驗證 | 前端 Canvas 交互,後端驗證軌跡/座標,安全性更高 |
| Redis 儲存驗證碼 | 分散式部署時,Session 替換為 Redis,設定 TTL 60-120 秒 |
| 速率限制 | 同一 IP/Session 限制生成頻率(如 10 次/分鐘) |
| 無障礙支援 | 提供音頻驗證碼選項(語音朗讀字元) |
Cache-Control: no-store 防止瀏覽器快取自建 GD 驗證碼適合學習、內部系統、低風險場景。面向公網的關鍵業務(登入、註冊、支付),**強烈建議接入成熟的第三方行為驗證服務**,兼顧安全性與用戶體驗。