“纸上得来终觉浅,绝知此事要躬行。” —— 陆游《冬夜读书示子聿》
在互联网的世界里,网站就像一座城堡,而登录界面则是它的正门。如果这扇门没有锁好,黑客就像“潜入者”,随时可能破门而入。
为了防止机器人、暴力破解等恶意行为,我们可以在登录页加入一个数学题验证码功能。今天,就让我们一起动手实现这个小功能——即使你是编程新手,也能一步步完成!
🧩 一、我们的目标:打造一个会出题的登录门卫
我们要做的,是在 WordPress 默认登录页面 上添加一个简单的验证码功能:
- 显示一道数学题
- 用户必须输入正确答案才能登录
- 错了?不准进!提示错误信息
- 验证码过期?可以刷新重来
听起来是不是很酷?别担心,接下来一步一步带你实现它。
🛠️ 二、准备工作:你不需要是程序员,但需要知道这些事
✅ 1. WordPress 插件机制简介
WordPress 是一个“可扩展”的系统,就像乐高积木,你可以通过插件给它加新功能。
我们不会改动 WordPress 的核心文件,而是创建一个自己的插件,这样既安全又容易维护。
✅ 2. PHP 基础知识(不用太深奥)
本教程使用的是 PHP,因为 WordPress 就是用 PHP 写的。你不需要成为高手,只要了解以下几点就够了:
session_start()
:开启会话,保存验证码结果imagepng()
:动态生成图片add_action()
和add_filter()
:这是 WordPress 的钩子系统,可以理解为“插入自定义代码的位置”
✅ 3. 开发工具推荐
有了这些工具,你就可以开始搭建你的“数字堡垒”了。
🔁 三、验证码的工作原理:像门卫查身份证一样严谨
验证码的核心逻辑其实很简单,就像门卫查身份证一样:
- 系统生成一个数学题,比如:“5 + 3 = ?”
- 把正确答案存在服务器上(Session)
- 用户输入答案后,系统比对是否一致
- 如果不一致或过期,就拦住不让进
整个过程就像是设置了一道“身份验证关卡”,确保来的不是机器人,而是真实用户。
📦 四、动手实战:手把手教你写出属于你的验证码插件
🔧 第一步:创建插件目录和主文件
进入你的 WordPress 安装目录:
/wp-content/plugins/
新建一个文件夹,例如:captcha-login
在这个文件夹中,新建一个文件:captcha-login.php
打开这个文件,写入如下内容作为插件头部:
<?php
/*
Plugin Name: Captcha for Login
Description: 在 WordPress 登录页添加数学验证码功能
Version: 1.0
Author: Your Name
*/
if (!defined('ABSPATH')) {
exit; // 禁止直接访问
}
// 加载核心逻辑文件
include plugin_dir_path(__FILE__) . 'includes/functions.php';
恭喜你,已经完成了第一步!
🧮 第二步:创建核心文件
在插件目录下新建一个文件夹:
/captcha-login/includes/
然后创建 functions.php
文件,写入以下代码:
<?php
if (!defined('ABSPATH')) {
exit;
}
// 启动Session
add_action('init','start_session');
function start_session() {
if (!session_id()) {
@session_start();
}
}
// 添加验证码字段到登录表单
add_action('login_form', 'display_captcha_on_login');
function display_captcha_on_login() {
echo '<p>
<label for="operation_captcha">验证码结果为:</label><br />
<input type="text" name="operation_captcha" id="operation_captcha" size="20" tabindex="30" />
<a href="#" id="refresh-captcha"><img src="' . esc_url(plugins_url('refresh.png', __FILE__)) . '" alt="刷新验证码" /></a>
<img src="' . esc_url(home_url('/captcha.png?t='. time())) . '" alt="验证码图片" style="vertical-align:middle;" />
</p>';
echo '<script>
document.getElementById("refresh-captcha").addEventListener("click", function(e) {
e.preventDefault();
var img = document.querySelector("img[alt=验证码图片]");
img.src = "' . esc_url(home_url('/captcha.png?t='. time())) . '";
});
</script>';
}
// 注册重写规则以隐藏真实路径
add_action('init','captcha_rewrite_rule');
function captcha_rewrite_rule() {
add_rewrite_rule('^captcha\.png$', 'index.php?__captcha=1', 'top');
}
// 添加自定义查询变量
add_filter('query_vars','captcha_query_var');
function captcha_query_var($vars) {
$vars[] = '__captcha';
return $vars;
}
// 输出验证码图片
add_action('template_redirect','captcha_image');
function captcha_image() {
if (get_query_var('__captcha')) {
include_once plugin_dir_path(__FILE__) . '/captcha.php';
exit;
}
}
// 验证用户输入的验证码
add_filter('authenticate','validate_captcha_login', 10, 3);
function validate_captcha_login($user,$username,$password) {
// 只有在提交登录表单时才验证验证码
if (!isset($_POST['log']) || !isset($_POST['pwd'])) {
return $user;
}
//+ 空字符串检查
$name = $_POST['operation_captcha'] ?? '';
if ($name === '') {
wp_redirect(wp_login_url() . '?cptchaerror=null');
exit;
}
//生成验证码运算结果检查
if (!isset($_SESSION['mcaptcha_result'])) {
wp_redirect(wp_login_url() . '?cptchaerror=resultnull');
exit;
}
//验证码有效期检查
if (isset($_SESSION['captcha_expiry']) && time() > $_SESSION['captcha_expiry']) {
wp_redirect(wp_login_url() . '?cptchaerror=expired');
exit;
}
$submitted = intval($_POST['operation_captcha']);
$correct = isset($_SESSION['captcha_result']) ? intval($_SESSION['captcha_result']) : null;
//验证码校验
if ($submitted !== $correct) {
wp_redirect(wp_login_url() . '?cptchaerror=wrong');
exit;
}
// 验证码通过后,继续执行 WordPress 默认的登录验证
return wp_authenticate_username_password(null, $username, $password);
}
// 在wp-login.php页面显示错误信息
add_action('login_message', 'display_captcha_error');
function display_captcha_error($message) {
if (isset($_GET['cptchaerror'])) {
if ($_GET['cptchaerror'] === 'null' || $_GET['cptchaerror'] === 'resultnull') {
$message .= '<div class="message error"><p><strong>ERROR</strong>: 请输入验证码。</p></div>';
} elseif ($_GET['cptchaerror'] === 'wrong') {
$message .= '<div class="message error"><p><strong>ERROR</strong>: 验证码不正确,请重试。</p></div>';
} elseif ($_GET['cptchaerror'] === 'expired') {
$message .= '<div class="message error"><p><strong>ERROR</strong>: 验证码已过期,请刷新重试。</p></div>';
}
}
return $message;
}
// 清理Session数据
add_action('wp_logout','clear_captcha_session');
add_action('wp_login','clear_captcha_session');
function clear_captcha_session() {
if (isset($_SESSION['captcha_result'])) {
unset($_SESSION['captcha_result']);
}
if (isset($_SESSION['captcha_expiry'])) {
unset($_SESSION['mcaptcha_expiry']);
}
if (isset($_SESSION['captcha_question'])) {
unset($_SESSION['captcha_question']);
}
}
📌 该文件拥有以下功能:
- 启动Session
- 添加验证码功能到登录界面
- 注册重写规则以隐藏真实路径
- 添加自定义查询变量
- 引用输出验证码图片
- 验证用户输入的验证码
- 在wp-login.php页面显示错误信息
- 清理Session数据。
🖼️ 第三步:生成验证码图片
在/captcha-login/includes/
路径下创建 captcha.php
文件,写入以下代码:
<?php
header("Content-type: image/png");
//写一个函数,让它自动出题
function math_Operation_captcha() {
$operators = ['+', '-', '×', '÷'];
$op = $operators[array_rand($operators)];
$num1 = rand(1, 10);
$num2 = rand(1, 10);
switch ($op) {
case '+':
$result = $num1 + $num2;
break;
case '-':
$result = $num1 - $num2;
break;
case '×':
$result = $num1 * $num2;
break;
case '÷':
while ($num2 == 0 || $num1 % $num2 != 0) {
$num1 = rand(1, 10);
$num2 = rand(1, 10);
}
$result = $num1 / $num2;
break;
}
//返回一个类似 "5 × 7" 的题目,并把正确答案保存在 $_SESSION 中,供后续验证使用。
$_SESSION['captcha_result'] = $result;
$_SESSION['captcha_question'] = "$num1 $op $num2";
$_SESSION['captcha_expiry'] = time() + 60; // 验证码有效期 60 秒
return "$num1 $op $num2";
}
$im = imagecreatetruecolor(120, 40);
$bg_color = imagecolorallocate($im, 255, 255, 255); // 白色背景
$text_color = imagecolorallocate($im, 0, 0, 0); // 黑色文字
$line_color = imagecolorallocate($im, 192, 192, 192); // 灰色干扰线
$noise_color = imagecolorallocate($im, 220, 220, 220); // 噪点颜色
imagefilledrectangle($im, 0, 0, 119, 39, $bg_color);
// 干扰线
for ($i = 0; $i < 3; $i++) {
imageline($im, rand(0, 120), 0, rand(0, 120), 40, $line_color);
}
// 噪点
for ($i = 0; $i < 50; $i++) {
imagesetpixel($im, rand(0, 120), rand(0, 40), $noise_color);
}
// 获取图片内容
$question =mc_generate_math_captcha();
// 使用SimHei.ttf字体防止图片内容乱码
imagettftext($im, 14, 0, 10, 22, $text_color, plugin_dir_path(__FILE__) . '/fonts/SimHei.ttf', $question);
imagepng($im);
imagedestroy($im);
这段代码会生成一张带干扰线的验证码图片,看起来更专业也更安全。
在includes/
路径下创建文件夹fonts
,将SimHei.ttf字体文件放入fonts
文件夹中。
🔁 第四步:刷新按钮功能
让用户能手动刷新验证码,就像换一道题再答一次:
上传一张名为 refresh.png
的刷新图片到/captcha-login/includes/
路径下,注意该图片大小要适配自己验证码图片的大小,否则就不美观。

🚨 五、常见问题与避坑指南
❗ 1. Session 启动失败
- 表现:验证码始终为空、无法保存、总是过期
- 解决方法:
- 使用
session_start()
前不能有任何输出(包括空格) - 推荐挂载到
init
钩子中启动 Session - 可以用
@session_start()
抑制警告
❗ 2. 验证码图片空白或乱码
- 原因:
- 文件开头有空格或 BOM 头
- PHP的GD 库未启用
- 图片路径不对
- 没有适配的字体文件
- 解决方法:
- 删除文件开头多余的空行
- 确保服务器开启了
GD
扩展 - 修改图片路径为正确的路径
- 下载字体文件,通过字体文件防止乱码
❗ 3. 验证码错误或过期,提交登录后仍然可以直接登录
- 原因:
WP_Error不生效
或者返回了$user
- 解决方法:
- 验证失败时用
wp_redirect()
跳转并结束脚本执行(exit
)
❗ 4. 验证码图片 URL 缓存问题
- 原因:
- 浏览器缓存导致一直显示旧验证码
- 解决方法:
- 在图片 URL 加时间戳:
?t=1234567890
🧩 总结:从零到一,完成自己的安全插件!
回顾一下我们做了什么:
- 创建插件结构,设置插件基本信息
- 生成随机数学题,保存答案到 Session
- 动态生成验证码图片
- 在登录表单中插入验证码字段和图片
- 使用 WordPress 重写规则隐藏图片路径
- 在登录流程中验证用户输入
- 添加前端提示,让用户知道哪里出错了
- 清理 Session,提升安全性
每一步都像是搭积木,最终拼成了一座坚固的“安全之塔”。
🎉 结语:自己写的插件,才是最安心的盾牌
虽然整个过程有点复杂,但只要你一步步跟着做,你会发现编写 WordPress 插件并没有想象中那么难。
一旦你完成了这个验证码功能,你就掌握了 WordPress 插件开发的精髓。
📌 小贴士:开发过程中一定要做的事
- 开启调试模式:
define('WP_DEBUG', true);
- 查看日志:
/wp-content/debug.log
- 使用浏览器开发者工具查看网络请求和表单数据
- 经常清除浏览器缓存,尤其是验证码图片
- 学会用
error_log("验证码不正确")
记录调试信息