@flexem/aj-captcha
v1.0.2
Published
AJ-Captcha 前端 JS SDK,支持滑动拼图和点选文字验证码
Readme
AJ-Captcha .NET 版
服务器域名: captcha.fbox360.com
行为验证码 C# WebAPI 实现,从 anji-plus/captcha Java 版翻译而来。
支持两种验证码类型:
- blockPuzzle — 滑动拼图(拖动滑块到缺口位置)
- clickWord — 点选文字(按顺序点击图片中指定的汉字)
API 接口
所有接口均为 POST,Content-Type: application/json。
POST /captcha/get — 获取验证码
请求体
{
"captchaType": "blockPuzzle"
}captchaType 可选值:blockPuzzle / clickWord / default(默认使用 blockPuzzle)
响应(blockPuzzle)
{
"repCode": "0000",
"repMsg": "success",
"repData": {
"token": "a1b2c3d4...",
"secretKey": "AbCdEfGh12345678",
"originalImageBase64": "iVBORw0KGgo...",
"jigsawImageBase64": "iVBORw0KGgo..."
}
}响应(clickWord)
{
"repCode": "0000",
"repMsg": "success",
"repData": {
"token": "a1b2c3d4...",
"secretKey": "AbCdEfGh12345678",
"originalImageBase64": "iVBORw0KGgo...",
"wordList": ["验", "码", "点"]
}
}POST /captcha/check — 校验用户操作
请求体(blockPuzzle)
{
"captchaType": "blockPuzzle",
"token": "a1b2c3d4...",
"pointJson": "<AES加密后的坐标JSON>"
}pointJson 是前端用 secretKey 对坐标对象 {"x":200,"y":5} 做 AES/ECB/PKCS7 加密后的 Base64 字符串。
请求体(clickWord)
{
"captchaType": "clickWord",
"token": "a1b2c3d4...",
"pointJson": "<AES加密后的坐标数组JSON>"
}pointJson 解密后为点击坐标数组:[{"x":85,"y":34},{"x":129,"y":56},{"x":233,"y":27}]
响应(校验通过)
{
"repCode": "0000",
"repMsg": "success",
"repData": {
"token": "a1b2c3d4...",
"result": true
}
}POST /captcha/internal/verify-v1 — 服务端二次校验
由业务后端调用,用于最终确认前端的验证结果有效。 请求头 带上 "x-secret" ,值的话问服务端后台人员单独给
请求体
{
"captchaVerification": "<二次校验值>"
}captchaVerification 的计算方式见下文"两阶段验证协议"章节。
响应(校验通过)
{
"repCode": "0000",
"repMsg": "success"
}错误码
| repCode | 含义 |
|---------|------|
| 0000 | 成功 |
| 6110 | 验证码已失效(token 过期或已使用),请重新获取 |
| 6111 | 坐标校验失败(位置不对) |
| 6112 | 获取验证码失败(底图未初始化) |
工作原理
整体流程
前端 验证码服务 业务后端
│ │ │
│─── POST /captcha/get ─────>│ │
│<── token + secretKey + 图 │ │
│ base64底图 │ │
│ │ │
│ 用户完成交互 │ │
│ (拖滑块 / 点文字) │ │
│ │ │
│─── POST /captcha/check ───>│ │
│ token + AES加密坐标 │ │
│<── result: true │ │
│ │ │
│ 前端计算 captchaVerification │
│ │ │
│─────────────── 携带 captchaVerification 提交业务表单 ──>│
│ │ │
│ │<── POST /captcha/verify ───│
│ │ captchaVerification │
│ │──> repCode: 0000 ─────────>│
│ │ │
│<──────────────────── 业务响应(登录成功等) ───────────│两阶段验证协议
防止重放攻击的核心机制:
阶段一:get + check
GET /captcha/get- 服务端生成随机坐标
(x, y)和 AES 密钥secretKey - 将
token → {x, y, secretKey}存入缓存,有效期 2 分钟 - 返回给前端:
token、secretKey、图片 base64
- 服务端生成随机坐标
POST /captcha/check- 前端用
secretKey对坐标加密后发送 - 服务端从缓存取出坐标,立即删除(一次性使用,防重放)
- 验证坐标是否匹配(blockPuzzle 允许 ±5px 误差)
- 验证通过后,计算:
value = AES_Encrypt(token + "---" + pointJson, secretKey) - 将
"second-{value}" → token存入缓存,有效期 3 分钟 - 返回
result: true
- 前端用
阶段二:verify
前端(或业务后端)计算
captchaVerification:// 前端用同样的 secretKey 计算(与服务端一致) captchaVerification = AES_Encrypt(token + "---" + pointJson, secretKey)POST /captcha/verify- 服务端查找
"second-{captchaVerification}"是否在缓存中 - 找到则立即删除,返回成功(一次性使用)
- 找不到则返回
6110
- 服务端查找
安全保证:
- token 一次性:check 后立即失效,防止重复提交
- secondKey 一次性:verify 后立即失效,防止重放
- captchaVerification 由 check 时的参数派生,无法伪造
blockPuzzle 图片生成算法
原始底图 (310×155) 模板滑块图 (约56×56, 含透明通道)
│ │
└──── 随机生成坐标 (x, y) ────┘
│
遍历模板每个像素 (i, j):
┌─────────────────────────────────────────┐
│ 若模板像素 alpha > 0(非透明): │
│ → 将原图 (x+i, y+j) 的像素复制到新滑块 │
│ → 对原图该像素做 3×3 高斯模糊(挖洞) │
│ │
│ 若当前像素与右邻/下邻透明性不同(边界): │
│ → 新滑块该像素设为白色(描边) │
│ → 原图对应位置设为白色(轮廓提示) │
└─────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
返回原图(含模糊洞+白色轮廓) 返回新滑块(透明背景+原图像素)高斯模糊算法(对应 Java avgMatrix):
取目标像素周围 3×3 邻域的 6 个像素(跳过中间列)
avg_R = (R1+R2+R3+R4+R5+R6) / 8
avg_G = (G1+G2+G3+G4+G5+G6) / 8
avg_B = (B1+B2+B3+B4+B5+B6) / 8clickWord 文字生成算法
1. 加载随机点选底图
2. 随机选取 N 个不重复汉字(来自 500+ 常用汉字集)
3. 对每个汉字:
- 按宽度分区随机放置坐标(避免重叠)
- 随机颜色 RGB(1-255, 1-255, 1-255)
- 随机旋转角度 [-45°, 45°]
- 用 WenQuanZhengHei.ttf 字体绘制
4. 随机隐藏其中 1 个汉字(从 wordList 和校验列表中排除)
5. 在右下角绘制水印
6. 返回:底图 base64 + wordList(需点击的 N-1 个汉字,按顺序)
7. 缓存:N-1 个汉字的坐标,用于 check 时校验AES 加密规范
与前端交互时的加密参数:
| 项目 | 值 |
|------|-----|
| 算法 | AES |
| 模式 | ECB |
| 填充 | PKCS7 |
| 密钥长度 | 128 bit(16字节) |
| 密钥来源 | GET /captcha/get 返回的 secretKey |
| 编码 | Base64 |
blockPuzzle pointJson 加密内容:
{"x": 215, "y": 5}clickWord pointJson 加密内容:
[{"x": 85, "y": 34}, {"x": 129, "y": 56}, {"x": 233, "y": 27}]配置项
appsettings.json 中 Captcha 节:
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| Type | default | 验证码类型:default/blockPuzzle/clickWord |
| CacheType | local | 缓存类型:local(内存)|
| WaterMark | 我的水印 | 右下角水印文字 |
| WaterFont | WenQuanZhengHei.ttf | 水印字体 |
| FontType | WenQuanZhengHei.ttf | 点选验证码字体 |
| FontSize | 25 | 点选文字大小(px) |
| SlipOffset | 5 | 滑动验证允许误差(px) |
| AesStatus | true | 是否开启 AES 坐标加密 |
| InterferenceOptions | 2 | 干扰滑块数量:0/1/2 |
| ClickWordCount | 4 | 点选文字总数(其中 1 个不参与校验) |
| Jigsaw | null | 自定义滑动验证底图目录(空=使用默认) |
| PicClick | null | 自定义点选验证底图目录(空=使用默认) |
项目结构
src/AjCaptcha.WebApi/
├── Configuration/
│ └── CaptchaOptions.cs 配置项映射类
├── Controllers/
│ └── CaptchaController.cs HTTP 接口(get/check/verify)
├── Models/
│ ├── CaptchaVO.cs 请求/响应数据对象
│ ├── PointVO.cs 坐标对象(x, y, secretKey)
│ └── ResponseModel.cs 统一响应格式
├── Services/
│ ├── ICaptchaService.cs 验证码服务接口
│ ├── ICaptchaCacheService.cs 缓存服务接口
│ ├── CaptchaServiceFactory.cs 按类型路由服务
│ └── Impl/
│ ├── AbstractCaptchaService.cs check/verify 公共逻辑
│ ├── BlockPuzzleCaptchaService.cs 滑动拼图实现
│ ├── ClickWordCaptchaService.cs 点选文字实现
│ └── MemoryCaptchaCacheService.cs 内存缓存实现
├── Utils/
│ ├── AesUtil.cs AES 加解密
│ ├── ImageUtils.cs 底图加载 + SkiaSharp 图片处理
│ ├── RandomUtils.cs 随机数/UUID/随机字符串
│ └── Md5Util.cs MD5 工具
└── Resources/
├── defaultImages/
│ ├── jigsaw/original/ 滑动验证底图(7张)
│ ├── jigsaw/slidingBlock/ 滑块模板图(6张,带透明通道)
│ └── pic-click/ 点选验证底图(17张)
└── fonts/
└── WenQuanZhengHei.ttf 开源中文字体扩展说明
接入 Redis 缓存
- 添加 NuGet 包:
StackExchange.Redis - 实现
ICaptchaCacheService接口,使用 Redis 操作替换内存操作 - 在
Program.cs中替换注册:// 替换这行 builder.Services.AddSingleton<ICaptchaCacheService, MemoryCaptchaCacheService>(); // 改为 builder.Services.AddSingleton<ICaptchaCacheService, RedisCaptchaCacheService>();
多节点部署时必须使用 Redis,内存缓存只适合单节点。
使用自定义底图
将图片(PNG/JPG)放入任意目录,在 appsettings.json 中配置路径:
{
"Captcha": {
"Jigsaw": "D:/myimages/jigsaw",
"PicClick": "D:/myimages/pic-click"
}
}添加新的验证码类型
- 继承
AbstractCaptchaService,实现Get()和ValidateCoordinates() - 在
Program.cs注册:builder.Services.AddSingleton<ICaptchaService, MyNewCaptchaService>();
前端 JS SDK
零依赖,兼容 IE9+,支持 TypeScript。
安装
npm install @flexem/aj-captcha或直接引入:
<script src="aj-captcha.min.js"></script>TypeScript 导入
// esModuleInterop: true
import AJCaptcha from '@flexem/aj-captcha';
// 或 CommonJS 风格
import AJCaptcha = require('@flexem/aj-captcha');弹窗模式(推荐)
AJCaptcha.popup({
server: 'https://captcha.fbox360.com',
captchaType: 'blockPuzzle', // 或 'clickWord'
title: '请完成安全验证',
lang: {
sliderTip: '向右拖动滑块填充拼图',
clickTip: '请依次点击:',
refreshTitle: '刷新',
checking: '验证中...',
verifySuccess: '验证通过',
verifyFail: '验证失败,请重试',
getFail: '获取验证码失败',
checkFail: '校验失败',
parseFail: '解析响应失败',
networkError: '网络错误: ',
popupTitle: '请完成安全验证'
},
onSuccess: function (verification) {
console.log('验证通过:', verification);
// 将 verification 提交给业务接口做二次校验
},
onFail: function (err) {
console.error('验证失败:', err);
},
onClose: function () {
console.log('用户关闭弹窗');
}
});lang 中所有字段均可选,未传则使用默认中文文案。
内嵌模式
var captcha = new AJCaptcha({
server: 'https://captcha.fbox360.com',
captchaType: 'clickWord',
el: '#captcha-container', // 或传入 DOM 元素
onSuccess: function (verification) {
console.log(verification);
}
});
captcha.render();API
| 方法 | 说明 |
|------|------|
| AJCaptcha.popup(options) | 弹窗验证,返回 { close, instance } |
| instance.render() | 渲染验证码到 el |
| instance.get(callback) | 手动获取验证码数据 |
| instance.check(pointData, callback) | 手动校验坐标 |
| instance.getCaptchaVerification() | 获取加密后的验证凭证 |
| AJCaptcha.AesUtil.encrypt(text, key) | AES/ECB/PKCS7 加密工具 |
