Node.js验证码:从生成到验证的趣味探索之旅
📜 引言:为何需要验证码?
设想你经营着一家热门咖啡店,每日有大量顾客前来消费。突然有机器人不断下单,导致真实顾客难以买到饮品。验证码就如同咖啡店的‘人工甄别系统’,确保每个请求都来自真实的人类顾客。
在Web开发领域,验证码(CAPTCHA)主要发挥以下作用:
- ✅ 抵御机器人暴力破解 (例如登录密码的暴力尝试)
- ✅ 保护敏感接口 (像查询个人资料的接口)
- ✅ 减轻服务器负担 (过滤掉无效的请求)
验证码是现代Web应用中常见的安全机制,能有效防范机器人的恶意攻击。接下来,我们将深入探究Node.js中验证码的生成与验证过程,这就像一场充满趣味的探索之旅!
1. 验证码的基础原理 🧠
验证码(CAPTCHA)的全称是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)。其核心思想是:
“创建一种人类易于识别但计算机难以识别的测试”
验证码工作流程示意图
是
否
客户端请求验证码
服务器生成SVG验证码
转换为PNG图片
返回Base64编码图片
用户提交表单+验证码
服务器校验验证码
验证通过?
执行业务逻辑
返回错误提示
2. 技术栈筹备 🛠️
在本次示例中,运用了以下关键技术:
技术 | 作用 | 特点 |
---|---|---|
express |
Web框架 | 轻巧灵活,用于处理HTTP请求 |
svg-captcha |
生成SVG验证码 | 纯JS实现,无需额外编译 |
sharp |
图像处理 | 高性能的图片转换工具 |
express-session |
会话管理 | 用于存储验证码文本 |
3. 验证码生成详解 🎨
让我们剖析代码里验证码生成的部分:
3.1 生成SVG验证码
const svgCaptcha = require('svg-captcha');
const sharp = require('sharp');
const session = require('express-session');
// 配置session中间件
router.use(session({
secret: 'stu_xpx_200701',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // 生产环境建议设置为true(HTTPS)
}));
// 生成验证码路由
router.get('/captcha', async (req, res) => {
// 创建包含4个字符的彩色验证码(排除易混淆字符)
const captcha = svgCaptcha.create({
size: 4, // 4个字符
ignoreChars: '0o1i', // 排除易混淆字符
noise: 2, // 干扰线数量
color: true // 彩色显示
});
// 存储验证码(带1分钟过期时间)
req.session.captcha = {
text: captcha.text.toLowerCase(), // 转为小写存储
expiresAt: Date.now() + 60000 // 当前时间+1分钟
};
💡 关键点:
参数 | 值 | 说明 |
---|---|---|
size | 4 | 验证码包含4个字符 |
ignoreChars | ‘0o1i’ | 排除数字0、字母o、数字1、字母i等易混淆字符 |
noise | 2 | 添加2条干扰线增加识别难度 |
color | true | 使用彩色而非黑白 |
3.2 转换为PNG格式
为何要将SVG转为PNG?
45% 30% 25% 图片格式选择原因 兼容性 安全性 性能
借助sharp
库进行转换:
try {
// 将SVG转为PNG(兼容更多浏览器)
const pngBuffer = await sharp(Buffer.from(captcha.data))
.png()
.toBuffer();
// 返回Base64编码的图片
res.type('png').json({
code: 0,
data: `data:image/png;base64,${pngBuffer.toString('base64')}`,
msg: "验证码图片获取成功"
});
} catch (err) {
console.error('生成PNG失败:', err);
res.status(500).json({ code: -1, msg: "生成验证码失败" });
}
🔧 技术细节:
Buffer.from()
将SVG字符串转为二进制数据sharp()
进行图片格式转换toString('base64')
生成前端可直接显示的DataURL
3.3 存储验证码信息
我们不仅存储验证码文本,还存储过期时间:
req.session.captcha = {
text: captcha.text.toLowerCase(), // 验证码文本
expiresAt: Date.now() + 1 * 60 * 1000 // 1分钟后过期
};
这种设计比单纯存储文本更安全,原因在于:
- 强制验证码有时效性
- 防止重放攻击
- 自动清理过期验证码
4. 验证码验证流程 🔍
当用户提交表单时,需要对验证码进行验证:
4.1 验证步骤分解
用户 服务器 提交表单(姓名,身份证,验证码) 检查必填字段 检查验证码是否存在 检查验证码是否过期 比较验证码文本 返回验证结果 用户
服务器
4.2 关键验证代码
router.post("/query", async (req, res) => {
// 检查必填参数
if (!req.body.studentname || !req.body.id_card || !req.body.code) {
return res.status(400).json({ code: -1, msg: "缺少必要参数" });
}
// 验证码三重校验
if (!req.session.captcha) {
return res.status(400).json({ code: -1, msg: "验证码已过期" });
}
if (req.session.captcha.expiresAt < Date.now()) {
return res.status(400).json({ code: -1, msg: "验证码已过期" });
}
if (req.session.captcha.text !== req.body.code.toLowerCase()) {
return res.status(400).json({ code: -1, msg: "验证码错误" });
}
// 验证通过后立即销毁验证码(一次性使用)
delete req.session.captcha;
// 验证码通过后的代码逻辑...
🛡️ 安全策略:
- 时间过期机制 :1分钟后自动失效
- 大小写无关校验 :统一转为小写比较
- 一次性使用 :验证后立即删除session存储
4.3 验证码的一次性使用
验证通过后立即删除验证码,避免重复使用:
// 验证通过后删除验证码(一次性使用)
delete req.session.captcha;
5. 成果查验
按照上述操作就能生成一个png地址,我们可以对其有效性进行检查,步骤如下:
生成的地址如:
返回地址:data:image/png;base64,${pngBuffer.toString('base64')}
例如:
data:image/png;base64,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