scr-slider-captcha
v1.1.2
Published
A Vue3 slider image captcha component with Node.js verification backend
Maintainers
Readme
scr-slider-captcha
一个 Vue3 图片滑块验证码组件 + Node.js 后端验证服务。前端采集用户拖动轨迹,通过 Web Worker 使用 RSA/AES-GCM 混合加密后发送至后端验证。
安装
npm install scr-slider-captcha前端使用
注意:导入组件时会自动引入样式文件,无需手动 import CSS。
完整流程
<script setup>
import { ref, onMounted } from 'vue';
import { SliderCaptcha } from 'scr-slider-captcha/frontend';
const bgBase64 = ref('');
const puzzleBase64 = ref('');
const targetY = ref(0);
const puzzleSize = ref(50);
const publicKey = ref('');
const captchaId = ref('');
const fetchCaptcha = async () => {
const res = await fetch('/api/captcha/create', { method: 'POST' });
const data = await res.json();
bgBase64.value = data.bgBase64;
puzzleBase64.value = data.puzzleBase64;
targetY.value = data.targetY;
puzzleSize.value = data.puzzleSize;
publicKey.value = data.publicKey;
captchaId.value = data.captchaId;
};
const handleVerify = async (encryptedTrajectory) => {
const res = await fetch('/api/captcha/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ captchaId: captchaId.value, encryptedTrajectory })
});
const result = await res.json();
if (result.success) {
// 验证通过
} else {
fetchCaptcha(); // 失败后刷新验证码
}
};
onMounted(fetchCaptcha);
</script>
<template>
<SliderCaptcha
:bg-base64="bgBase64"
:puzzle-base64="puzzleBase64"
:target-y="targetY"
:puzzle-size="puzzleSize"
:width="300"
:height="150"
:public-key="publicKey"
@verify="handleVerify"
/>
</template>SliderCaptcha Props
| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | bgBase64 | String | '' | 带镂空的背景图片 base64(不含 data:image 头) | | puzzleBase64 | String | '' | 拼图块图片 base64 | | targetY | Number | 50 | 拼图块在背景中的垂直位置(像素) | | puzzleSize | Number | 50 | 拼图块尺寸(像素) | | width | Number | 300 | 验证码宽度(像素) | | height | Number | 150 | 验证码高度(像素) | | publicKey | String | '' | RSA 公钥 PEM 字符串,传入后启用加密 |
SliderCaptcha Events
| 事件 | 参数 | 说明 | |------|------|------| | verify | encryptedData: String | 用户拖动完成后触发,参数为加密后的轨迹数据,需发送至后端验证 |
加密说明
- 轨迹数据在 Web Worker 中加密,不阻塞主线程
- 数据量 < 400 字符:RSA-OAEP 直接加密(前缀
rsa:) - 数据量 >= 400 字符:AES-GCM 加密轨迹 + RSA-OAEP 加密 AES 密钥(前缀
hybrid:) - 未传入公钥:base64 编码明文(前缀
raw:)
后端使用
const { createCaptcha, verifyCaptcha } = require('scr-slider-captcha/backend');
// 创建验证码
const captcha = await createCaptcha({
width: 300,
height: 150,
puzzleSize: 50,
publicKey: '-----BEGIN PUBLIC KEY-----\n...' // 传入公钥返回给前端
});
// 验证轨迹
const result = verifyCaptcha(
captchaId,
encryptedTrajectory,
'-----BEGIN PRIVATE KEY-----\n...' // 传入私钥解密
);createCaptcha
异步函数,生成滑块验证码。
const captcha = await createCaptcha(options);options
| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | width | Number | 300 | 图片宽度 | | height | Number | 150 | 图片高度 | | puzzleSize | Number | 50 | 拼图块尺寸 | | publicKey | String | '' | RSA 公钥,传入后将返回给前端用于 Web Worker 加密 |
返回值
{
"captchaId": "uuid-string",
"bgBase64": "base64-string",
"puzzleBase64": "base64-string",
"targetY": 45,
"puzzleSize": 50,
"publicKey": "-----BEGIN PUBLIC KEY-----\n..."
}verifyCaptcha
同步函数,验证滑块轨迹。
const result = verifyCaptcha(captchaId, encryptedTrajectory, privateKey, scoreThreshold);参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| captchaId | String | - | createCaptcha 返回的验证码 ID |
| encryptedTrajectory | String | - | 前端 SliderCaptcha 组件 @verify 事件返回的加密数据 |
| privateKey | String | null | RSA 私钥 PEM 字符串,用于解密轨迹数据 |
| scoreThreshold | Number | 50 | 分数阈值,轨迹分数高于此值则验证通过(0-100) |
返回值
{ "success": true }成功返回 { success: true },失败返回 { success: false }。
验证规则
- 位置偏差:滑块最终位置与目标位置偏差 > 30px 则失败
- 无拖动:最终位置与目标位置完全一致(偏差为 0)判定为机器操作
- 轨迹分析:
- 总耗时 < 200ms 判定为过快
- 速度标准差 < 0.3 判定为速度过于均匀(类机器)
- 速度范围 < 0.5 判定为缺乏变化
- 方向变化超过轨迹点 30% 判定为抖动异常
- Y 轴方差 < 1 判定为完全水平(类机器)
- 缺少加减速曲线判定为非人操作
- 综合评分 < 50 分验证失败
密钥生成
使用 OpenSSL 生成 4096-bit RSA 密钥对:
# 生成私钥
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:4096
# 提取公钥
openssl pkey -in private.pem -pubout -out public.pem密钥由调用方管理,前端组件和后端包不包含密钥文件。通过 publicKey 和 privateKey 参数动态传入。
快速启动
后端服务器
const express = require('express');
const cors = require('cors');
const fs = require('fs');
const { createCaptcha, verifyCaptcha } = require('scr-slider-captcha/backend');
const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
const publicKey = fs.readFileSync('./keys/public.pem', 'utf8');
const privateKey = fs.readFileSync('./keys/private.pem', 'utf8');
app.post('/api/captcha/create', async (req, res) => {
const result = await createCaptcha({ ...req.body, publicKey });
res.json(result);
});
app.post('/api/captcha/verify', (req, res) => {
const { captchaId, encryptedTrajectory } = req.body;
const result = verifyCaptcha(captchaId, encryptedTrajectory, privateKey);
res.json(result);
});
app.listen(3000);前端集成
<script setup>
import { ref } from 'vue';
import { SliderCaptcha } from 'scr-slider-captcha/frontend';
const bgBase64 = ref('');
const puzzleBase64 = ref('');
const targetY = ref(0);
const puzzleSize = ref(50);
const publicKey = ref('');
const captchaId = ref('');
const fetchCaptcha = async () => {
const res = await fetch('http://localhost:3000/api/captcha/create', { method: 'POST' });
const data = await res.json();
bgBase64.value = data.bgBase64;
puzzleBase64.value = data.puzzleBase64;
targetY.value = data.targetY;
puzzleSize.value = data.puzzleSize;
publicKey.value = data.publicKey;
captchaId.value = data.captchaId;
};
const handleVerify = async (encryptedTrajectory) => {
const res = await fetch('http://localhost:3000/api/captcha/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ captchaId: captchaId.value, encryptedTrajectory })
});
const result = await res.json();
if (result.success) {
// 验证通过
} else {
fetchCaptcha(); // 刷新验证码
}
};
onMounted(fetchCaptcha);
</script>
<template>
<SliderCaptcha
:bg-base64="bgBase64"
:puzzle-base64="puzzleBase64"
:target-y="targetY"
:puzzle-size="puzzleSize"
:public-key="publicKey"
@verify="handleVerify"
/>
</template>包结构
scr-slider-captcha/
├── frontend/
│ ├── scr-slider-captcha.es.js ESM 模块(已混淆压缩)
│ ├── scr-slider-captcha.umd.js UMD 模块
│ └── style.css 组件样式
├── backend/
│ ├── index.js 主入口(createCaptcha, verifyCaptcha)
│ ├── verifier.js 轨迹验证逻辑
│ ├── crypto.js 解密模块
│ └── imageProcessor.js 图片获取
└── package.json依赖
| 依赖 | 版本 | 用途 | |------|------|------| | canvas | ^3.2.3 | 后端图片裁切与拼图生成(Node.js Canvas) | | uuid | ^9.0.0 | 后端生成验证码 ID |
前端零依赖,后端仅依赖 Canvas 图片处理和 UUID 生成器。
许可证
MIT
