SVG验证码优于GD因矢量缩放无损、文件小、可内联;需手动构造XML,用<text>逐字渲染并加干扰线,注意字符集筛选、坐标取整、Header无前置输出及浏览器兼容性。

为什么不用 GD 而选 SVG 输出验证码
GD 生成的 PNG 验证码在高 DPI 屏幕上容易模糊,且抗锯齿控制麻烦;SVG 是矢量格式,缩放无损,文件小,还能直接内联到 HTML 中避免额外请求。但 PHP 默认不内置 SVG 渲染库,得手动构造 XML 结构,不能依赖 imagecreate 系列函数。
关键点在于:SVG 不是“绘制”,而是“描述”——你要拼出合法的 <svg> 标签结构,用 <text> 元素逐个放置字母,并控制字体、位置、旋转和随机干扰线。
如何用纯 PHP 生成带干扰的 SVG 验证码
核心是构造可读、防 OCR、又不至于人类难辨的文本图形。不要用 random_bytes 直接映射字符(易出空格或控制符),应限定字符集;干扰线不能盖住文字主体,旋转角度建议控制在 ±15° 内。
实操建议:
- 字符集用
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'(排除易混淆的 I/O/0/1) - 每个字符用独立
<text>元素,设置x、y、transform="rotate()"和font-size - 干扰线用
<line>,坐标随机但避开文字主区域(比如 y 在 20–40 之间,而文字 baseline 在 50 左右) - 必须输出
Content-Type: image/svg+xml,且不能有任何前置输出(包括 BOM、空行、echo之前空格)
<?php
header('Content-Type: image/svg+xml');
$code = substr(str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZ23456789'), 0, 4);
$width = 120;
$height = 50;
echo "<svg width=\"{$width}\" height=\"{$height}\" xmlns=\"http://www.w3.org/2000/svg\">";
// 干扰线
for ($i = 0; $i < 3; $i++) {
$x1 = rand(0, $width); $y1 = rand(5, 15);
$x2 = rand(0, $width); $y2 = rand(35, 45);
echo "<line x1=\"{$x1}\" y1=\"{$y1}\" x2=\"{$x2}\" y2=\"{$y2}\" stroke=\"#ccc\" stroke-width=\"1\"/>";
}
// 字符
$x_step = $width / strlen($code);
for ($i = 0; $i < strlen($code); $i++) {
$char = $code[$i];
$x = $x_step * $i + $x_step * 0.5;
$y = 35 + rand(-5, 5);
$rotate = rand(-12, 12);
echo "<text x=\"{$x}\" y=\"{$y}\" font-family=\"sans-serif\" font-size=\"24\" fill=\"#333\" text-anchor=\"middle\" transform=\"rotate({$rotate} {$x} {$y})\">{$char}</text>";
}
echo "</svg>";
?>
常见错误:浏览器显示空白或下载 SVG 文件
最常踩的坑是 PHP 输出前有不可见字符(如 UTF-8 BOM)、header() 调用前有空行、或 Web 服务器配置强制给 .php 响应加了 Content-Type: text/html。
排查步骤:
- 用
curl -I your-script.php检查响应头是否含Content-Type: image/svg+xml - 用
php -l your-script.php确保语法无错,且文件保存为 UTF-8 无 BOM - Apache 用户需确认
AddType image/svg+xml .svg不影响 .php 文件;Nginx 用户检查是否误配了add_header Content-Type - 若嵌入 HTML,用
<img src="code.php">即可,不要用<object>或<iframe>—— 后者可能触发同源策略限制
安全与可用性边界在哪
SVG 验证码无法防止自动化识别(OCR + 字体还原比 PNG 更容易),仅适合低对抗场景;若需更高强度,必须配合服务端 session 校验、时间窗口限制、失败计数封禁等。另外,部分老旧浏览器(IE ≤ 11)不支持内联 SVG 的 <img> 引用,会显示红叉,此时应降级为 PNG fallback 方案。
真正容易被忽略的是:SVG 中所有属性值(如 x、y、rotate)必须是数字字符串,不能是浮点计算后带多余小数位(如 23.000000000000001),否则某些渲染引擎会报错或跳过该元素。建议对坐标做 round() 或 sprintf('%.0f', $val) 处理。
立即学习“PHP免费学习笔记(深入)”;



















