点击式行为验证码后端库 - Node.js implementation
npm install @aardpro/captcha-nodebash
npm
npm install @aardpro/captcha-node
yarn
yarn add @aardpro/captcha-node
pnpm
pnpm add @aardpro/captcha-node
bun
bun add @aardpro/captcha-node
`
$3
- Node.js >= 18.0.0
- 本库使用 sharp 进行图片处理(预编译二进制,无需额外依赖)
- 支持 Windows、Linux、macOS
快速开始
$3
`typescript
import { generateCaptcha } from '@aardpro/captcha-node';
const { image, token } = await generateCaptcha({
chars: 'ABCDEFGHJKMNPQRSTUVWXYZ23456789',
count: 4,
secret: 'your-secret-key-at-least-20-characters-long'
});
// image: data:image/png;base64,...
// token: 加密后的token字符串
`
返回结果:
- image: Base64 格式的 PNG 图片,可直接在前端显示
- token: 加密的 token,包含坐标和过期时间
$3
前端需要:
1. 显示 image 图片
2. 让用户按顺序(从左到右)点击图片上的字符
3. 收集点击坐标 [[x1, y1], [x2, y2], ...]
4. 将坐标和 token 发送到后端验证
$3
`typescript
import { verifyCaptcha } from '@aardpro/captcha-node';
const result = verifyCaptcha({
token: '从前端获取的token',
input: [[100, 150], [200, 250]], // 用户点击的坐标数组
secret: 'your-secret-key-at-least-20-characters-long'
});
// result: true (验证成功) 或 false (验证失败)
`
API 文档
$3
生成验证码图片和加密 token。
`typescript
function generateCaptcha(options: GenerateOptions): Promise
`
#### 参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| chars | string | ✅ | - | 字符池,可包含任意字符(包括中文),长度不能少于 count |
| count | number | ✅ | - | 显示在图片上的字符数量,范围 2-6 |
| secret | string | ✅ | - | 加密密钥,长度 20-256 字符 |
| exp | number | ❌ | 300 | 过期时间(秒),默认 5 分钟 |
| tolerance | number | ❌ | 10 | 容错半径(像素),用户点击允许的误差范围 |
| width | number | ❌ | 400 | 图片宽度(像素) |
| height | number | ❌ | 300 | 图片高度(像素) |
| margin | number | ❌ | 30 | 字符距离边缘的最小距离(像素) |
#### 返回值
`typescript
interface GenerateResult {
image: string; // Base64 PNG图片,格式: data:image/png;base64,...
token: string; // 加密token
}
`
#### 示例
`typescript
// 基本用法
const { image, token } = await generateCaptcha({
chars: 'ABCDEFGHJKMNPQRSTUVWXYZ23456789',
count: 4,
secret: 'my-secret-key-at-least-20-chars'
});
// 自定义参数
const { image, token } = await generateCaptcha({
chars: '验证码测试字符',
count: 6,
secret: 'my-secret-key-at-least-20-chars',
exp: 600, // 10分钟过期
tolerance: 15, // 容错半径15像素
width: 500,
height: 400
});
`
$3
验证用户点击的坐标是否正确。
`typescript
function verifyCaptcha(options: VerifyOptions): boolean
`
#### 参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| token | string | ✅ | 从 generateCaptcha 获取的 token |
| input | Array<[number, number]> | ✅ | 用户点击的坐标数组,必须按字符出现顺序(从左到右) |
| secret | string | ✅ | 与生成时使用的相同密钥 |
#### 返回值
- true: 验证成功
- false: 验证失败(坐标错误、过期或 token 无效)
#### 示例
`typescript
const isValid = verifyCaptcha({
token: '从generateCaptcha获取的token',
input: [
[100, 150], // 第一个字符的点击位置
[200, 250], // 第二个字符的点击位置
],
secret: 'my-secret-key-at-least-20-chars'
});
if (isValid) {
console.log('验证通过!');
} else {
console.log('验证失败:坐标错误、超时或token无效');
}
`
架构设计
$3
本库采用无状态设计,所有需要验证的信息都加密在 token 中:
`
生成验证码:后端生成 { image, token } → 都返回给前端
前端存储:前端保存 token(无法解密,无法伪造)
用户点击:前端收集坐标 input
验证请求:前端发送 { input, token } → 后端验证
后端验证:解密 token → 验证坐标 → 返回结果
`
$3
为什么这样设计是安全的?
1. 前端无法伪造 token
- token 使用 AES-256-CBC 加密
- 没有 secret 密钥,前端无法生成有效 token
2. 前端无法获取真实坐标
- 坐标信息加密存储在 token 中
- 前端只能看到加密字符串,无法解密
3. token 可以公开传输
- 即使 token 被拦截,没有 secret 也无法解密
- token 包含过期时间,自动失效
$3
- ✅ 无需 session/Redis:服务器不需要存储状态
- ✅ 水平扩展友好:多个服务器实例都可以验证
- ✅ 简单部署:不需要额外的存储服务
- ✅ API 简洁:只需两个接口(生成 + 验证)
$3
- ⚠️ token 可以多次使用(在过期时间内)
- 💡 如需防止重放,可在应用层添加已使用token的记录(见安全建议)
完整示例
$3
`typescript
import express from 'express';
import { generateCaptcha, verifyCaptcha } from '@aardpro/captcha-node';
const app = express();
app.use(express.json());
const SECRET = 'your-app-secret-key-at-least-20-characters-long';
// 生成验证码(无状态设计)
app.get('/api/captcha', async (req, res) => {
try {
const { image, token } = await generateCaptcha({
chars: 'ABCDEFGHJKMNPQRSTUVWXYZ23456789',
count: 4,
secret: SECRET
});
// 将 image 和 token 都返回给前端
// token 是加密的,前端无法解密,也无法伪造
res.json({ image, token });
} catch (error) {
res.status(500).json({ error: '生成验证码失败' });
}
});
// 验证验证码(无状态设计)
app.post('/api/verify', (req, res) => {
try {
const { input, token } = req.body;
if (!input || !token) {
return res.status(400).json({ error: '缺少必要参数' });
}
const isValid = verifyCaptcha({
token, // 从前端接收的加密token
input, // 用户点击的坐标
secret: SECRET
});
res.json({ valid: isValid });
} catch (error) {
res.status(500).json({ error: '验证失败' });
}
});
app.listen(3000);
`
$3
`html
验证码示例
请按顺序点击图片上的字符(从左到右)
`
安全建议
1. 密钥管理
- secret 应该存储在环境变量中,不要硬编码在代码里
- 使用足够长的密钥(至少20个字符)
- 定期更换密钥
2. 防重放攻击
- 本库采用无状态设计,token可以多次验证(只要未过期)
- 如需防止重放,建议在应用层实现:
- 使用 Redis 存储已使用的token(验证成功后set,过期时间与token一致)
- 或在token中添加nonce,验证成功后记录到数据库
- token本身有过期时间限制(默认5分钟)
3. 防暴力破解
- 在应用层面限制验证失败次数(如使用 Redis 计数)
- 失败次数过多时临时封禁 IP 或用户
- 不要返回具体的错误原因(如"坐标错误"或"已过期")
4. 过期时间
- 根据业务需求设置合理的过期时间
- 默认 5 分钟适用于大多数场景
- 敏感操作建议使用更短的过期时间
常见问题
$3
A: 本库使用预编译的二进制文件,通常不会出现安装问题。如果遇到问题:
1. 确保使用的是 Node.js >= 18.0.0
2. 尝试清除缓存:npm cache clean --force
3. 删除 node_modules 后重新安装
$3
A:
1. 使用更长的字符池(包含中文、数字、字母混合)
2. 增加字符数量(count 参数)
3. 缩短过期时间
4. 在应用层面添加频率限制
$3
A: 检查以下几点:
1. 确保用户按顺序点击(从左到右)
2. 确保坐标格式正确 [[x1, y1], [x2, y2], ...]
3. 确保 secret 字符串完全一致
4. 检查 token 是否已过期
$3
A: 当前版本使用内置的 5 张背景图。如需自定义,可以修改源码中的 src/images/backgrounds.ts 文件。
$3
A: 需要 Node.js >= 18.0.0
开发
`bash
克隆仓库
git clone
安装依赖
npm install
构建
npm run build
运行测试
npm test
开发模式(监听文件变化)
npm run dev
``