npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aardpro/captcha-node

v1.0.0

Published

点击式行为验证码后端库 - Node.js implementation

Readme

@aardpro/captcha-node

点击式行为验证码后端库 - Node.js 实现

功能特点

  • 🎯 点击式验证:用户按顺序点击图片上的字符
  • 🔒 安全加密:使用 AES-256-CBC 加密坐标数据
  • ⏰ 过期机制:支持自定义验证码过期时间
  • 🎨 随机干扰:内置5张精美背景图,字符随机旋转、缩放、变色
  • 🚀 零配置跨平台:使用 SVG + Sharp,无需原生依赖
  • 📦 开箱即用:合理的默认配置,减少配置负担
  • ✨ TypeScript:完整的类型定义

安装

使用你喜欢的包管理器安装:

# npm
npm install @aardpro/captcha-node

# yarn
yarn add @aardpro/captcha-node

# pnpm
pnpm add @aardpro/captcha-node

# bun
bun add @aardpro/captcha-node

系统要求

  • Node.js >= 18.0.0
  • 本库使用 sharp 进行图片处理(预编译二进制,无需额外依赖)
  • 支持 Windows、Linux、macOS

快速开始

1. 生成验证码

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,包含坐标和过期时间

2. 前端集成

前端需要:

  1. 显示 image 图片
  2. 让用户按顺序(从左到右)点击图片上的字符
  3. 收集点击坐标 [[x1, y1], [x2, y2], ...]
  4. 将坐标和 token 发送到后端验证

3. 后端验证

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 文档

generateCaptcha

生成验证码图片和加密 token。

function generateCaptcha(options: GenerateOptions): Promise<GenerateResult>

参数

| 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | 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 | 字符距离边缘的最小距离(像素) |

返回值

interface GenerateResult {
  image: string;  // Base64 PNG图片,格式: data:image/png;base64,...
  token: string;  // 加密token
}

示例

// 基本用法
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
});

verifyCaptcha

验证用户点击的坐标是否正确。

function verifyCaptcha(options: VerifyOptions): boolean

参数

| 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | token | string | ✅ | 从 generateCaptcha 获取的 token | | input | Array<[number, number]> | ✅ | 用户点击的坐标数组,必须按字符出现顺序(从左到右) | | secret | string | ✅ | 与生成时使用的相同密钥 |

返回值

  • true: 验证成功
  • false: 验证失败(坐标错误、过期或 token 无效)

示例

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无效');
}

架构设计

无状态设计(Stateless)

本库采用无状态设计,所有需要验证的信息都加密在 token 中:

生成验证码:后端生成 { image, token } → 都返回给前端
前端存储:前端保存 token(无法解密,无法伪造)
用户点击:前端收集坐标 input
验证请求:前端发送 { input, token } → 后端验证
后端验证:解密 token → 验证坐标 → 返回结果

安全性分析

为什么这样设计是安全的?

  1. 前端无法伪造 token

    • token 使用 AES-256-CBC 加密
    • 没有 secret 密钥,前端无法生成有效 token
  2. 前端无法获取真实坐标

    • 坐标信息加密存储在 token 中
    • 前端只能看到加密字符串,无法解密
  3. token 可以公开传输

    • 即使 token 被拦截,没有 secret 也无法解密
    • token 包含过期时间,自动失效

优势

  • 无需 session/Redis:服务器不需要存储状态
  • 水平扩展友好:多个服务器实例都可以验证
  • 简单部署:不需要额外的存储服务
  • API 简洁:只需两个接口(生成 + 验证)

权衡

  • ⚠️ token 可以多次使用(在过期时间内)
  • 💡 如需防止重放,可在应用层添加已使用token的记录(见安全建议)

完整示例

Express 集成示例

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);

前端示例(HTML + JavaScript)

<!DOCTYPE html>
<html>
<head>
  <title>验证码示例</title>
</head>
<body>
  <div id="captcha-container">
    <img id="captcha-image" src="" alt="验证码" style="cursor: pointer;" />
    <p>请按顺序点击图片上的字符(从左到右)</p>
    <button onclick="submitCaptcha()">提交</button>
    <button onclick="refreshCaptcha()">刷新</button>
  </div>

  <script>
    let clicks = [];
    let currentToken = '';

    // 加载验证码
    async function refreshCaptcha() {
      clicks = [];
      const response = await fetch('/api/captcha');
      const data = await response.json();

      // 保存token和显示图片
      currentToken = data.token;
      document.getElementById('captcha-image').src = data.image;
    }

    // 记录点击位置
    document.getElementById('captcha-image').addEventListener('click', (e) => {
      const rect = e.target.getBoundingClientRect();
      const x = Math.round(e.clientX - rect.left);
      const y = Math.round(e.clientY - rect.top);

      clicks.push([x, y]);

      // 可选:在点击位置显示标记
      showClickMarker(e.clientX, e.clientY);
    });

    // 提交验证
    async function submitCaptcha() {
      const response = await fetch('/api/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          input: clicks,
          token: currentToken  // 将token一起发送
        })
      });

      const data = await response.json();

      if (data.valid) {
        alert('验证通过!');
      } else {
        alert('验证失败,请重试');
        refreshCaptcha();
      }
    }

    // 页面加载时获取验证码
    refreshCaptcha();
  </script>
</body>
</html>

安全建议

  1. 密钥管理

    • secret 应该存储在环境变量中,不要硬编码在代码里
    • 使用足够长的密钥(至少20个字符)
    • 定期更换密钥
  2. 防重放攻击

    • 本库采用无状态设计,token可以多次验证(只要未过期)
    • 如需防止重放,建议在应用层实现:
      • 使用 Redis 存储已使用的token(验证成功后set,过期时间与token一致)
      • 或在token中添加nonce,验证成功后记录到数据库
    • token本身有过期时间限制(默认5分钟)
  3. 防暴力破解

    • 在应用层面限制验证失败次数(如使用 Redis 计数)
    • 失败次数过多时临时封禁 IP 或用户
    • 不要返回具体的错误原因(如"坐标错误"或"已过期")
  4. 过期时间

    • 根据业务需求设置合理的过期时间
    • 默认 5 分钟适用于大多数场景
    • 敏感操作建议使用更短的过期时间

常见问题

Q: 安装失败怎么办?

A: 本库使用预编译的二进制文件,通常不会出现安装问题。如果遇到问题:

  1. 确保使用的是 Node.js >= 18.0.0
  2. 尝试清除缓存:npm cache clean --force
  3. 删除 node_modules 后重新安装

Q: 如何提高验证码的安全性?

A:

  1. 使用更长的字符池(包含中文、数字、字母混合)
  2. 增加字符数量(count 参数)
  3. 缩短过期时间
  4. 在应用层面添加频率限制

Q: 为什么验证总是失败?

A: 检查以下几点:

  1. 确保用户按顺序点击(从左到右)
  2. 确保坐标格式正确 [[x1, y1], [x2, y2], ...]
  3. 确保 secret 字符串完全一致
  4. 检查 token 是否已过期

Q: 可以自定义背景图吗?

A: 当前版本使用内置的 5 张背景图。如需自定义,可以修改源码中的 src/images/backgrounds.ts 文件。

Q: 支持哪些 Node.js 版本?

A: 需要 Node.js >= 18.0.0

开发

# 克隆仓库
git clone <repository-url>

# 安装依赖
npm install

# 构建
npm run build

# 运行测试
npm test

# 开发模式(监听文件变化)
npm run dev

License

MIT

支持

如有问题或建议,请提交 Issue