简介
动态验证码管理系统 v2.4.2 — 纯 Python 实现的验证码服务(无第三方 Web 框架),提供滑动拼图、点选、文字三种验证码,内置 IP 限流、失败锁定、多用户体系与可选 Redis,配套 WordPress 接入插件。
功能总览
- 滑动拼图(轨迹/时序行为校验)、点选(按序点击)、文字(兼容旧接口)三种验证方式
- 多用户体系:管理员 / 普通用户双角色,用户组配额,开放注册(管理后台开关,注册需验证码 + IP 限流)
- 二次验证(TOTP):登录两步验证,绑定支持二维码扫码(Google/Microsoft Authenticator 等),管理员可重置用户 2FA
- 登录验证码:后台登录默认需图片验证码(防暴力破解)
- 多 API Key 管理、每 Key 连接配置一键复制、按 Key 使用统计
- 演示页 Key 隔离:demo/文档页使用自动轮换的受限演示 Key,业务 Key 不泄露
- IP 限流(按 action 独立配额)、失败锁定、pass_token 一次性校验
- 可选 Redis(Token 自动过期 + 多实例共享限流);可选 PostgreSQL 存储(设置
DATABASE_URL即切换,默认 SQLite);HTTP/1.1 keep-alive(复用连接,前端响应更快) - 构建自动选择国内/国际镜像源(基础镜像 + apt + pip)
技术栈
Python 3.10+ / http.server(线程化)/ Pillow / PyJWT / qrcode / SQLite(可选 PostgreSQL、redis)
快速开始
本地运行
# 1. 安装依赖(可选 redis)
pip install -r requirements.txt
# 2. 设置生产凭据(ENV=production 下默认凭据会拒绝启动)
export SECRET_KEY=$(openssl rand -hex 32)
export ADMIN_PASS='强密码'
export DEFAULT_API_KEY='业务用的 Key'
# 3. 启动
python3 app.py
默认监听 0.0.0.0:8080。生产环境请固定 SECRET_KEY(重启后 JWT 不会失效)。
Docker 部署(直接拉取镜像,无需本地构建)
# ① 拉取编排文件与配置模板(无需 clone 仓库)
curl -O https://raw.githubusercontent.com/chinachat/captcha_system/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/chinachat/captcha_system/main/.env.example
# ② 配置生产凭据(.env 不入库)
cp .env.example .env
# 编辑 .env 填入 SECRET_KEY / ADMIN_PASS / DEFAULT_API_KEY(可用 openssl rand -hex 32 生成)
# ③ 启动(自动拉取 ghcr.io/chinachat/captcha_system,多架构 amd64/arm64)
docker compose up -d captcha
docker compose logs -f captcha
# 更新:docker compose pull && docker compose up -d
也可本地构建(开发/无外网拉取场景):./build.sh(自动探测 daocloud → 阿里云 → Docker Hub 基础镜像源)后 docker compose up -d --build。
使用 PostgreSQL 存储(可选)
默认 SQLite(单文件零配置,DB_PATH 持久化即可)。需要 PostgreSQL 时:.env 设置 POSTGRES_PASSWORD,取消注释 docker-compose.yml 中 postgres 服务与 DATABASE_URL 环境变量,启动后自动建表迁移。两种存储的接口与行为完全一致;切换存储需自行迁移已有数据。
访问地址
| 地址 | 说明 |
|---|---|
| http://127.0.0.1:8080/ | 项目首页(科技感落地页,内置登录/注册) |
| http://127.0.0.1:8080/demo | 验证码在线演示页 |
| http://127.0.0.1:8080/admin | 管理后台(登录需验证码) |
| http://127.0.0.1:8080/api/v1/health | 健康检查 |
| http://127.0.0.1:8080/api/v1/docs | API 文档(JSON) |
默认管理员:admin/ 密码取ADMIN_PASS环境变量(默认admin123,生产必须修改)。普通用户由管理员在后台"用户管理"创建,或在后台「系统设置」开启开放注册后由用户自助注册。
环境变量
安全提示:设置 ENV=production 后,若 SECRET_KEY / ADMIN_PASS / DEFAULT_API_KEY 仍为默认值,应用将拒绝启动(fail-fast)。Docker 部署建议全部写入 .env。
| 变量 | 默认值 | 说明 |
|---|---|---|
HOST / PORT | 0.0.0.0 / 8080 | 监听地址与端口 |
ENV | development | production 启用默认凭据 fail-fast 校验 |
ALLOW_INSECURE_DEFAULTS | 空 | 生产环境也允许默认凭据(仅调试) |
SECRET_KEY | 随机(不持久) | JWT 签名密钥,生产必须显式设置并固定 |
PASS_TOKEN_SECRET | 回退 SECRET_KEY | 业务 pass_token 独立签发密钥(减小扩散面) |
ADMIN_USER / ADMIN_PASS | admin / admin123 | 内置管理员凭据 |
DEFAULT_API_KEY | demo-api-key-captcha-2026 | 默认 API Key |
CAPTCHA_EXPIRE | 120 | 验证码有效秒数 |
PASS_TOKEN_EXPIRE | 60 | pass_token(JWT)有效秒数 |
MAX_BODY_BYTES | 65536 | 请求体上限(防内存耗尽) |
REQUEST_TIMEOUT | 15 | 单连接读取超时(秒) |
MAX_CONCURRENT | 64 | 最大并发连接(超限拒绝新连接) |
DB_PATH | /tmp/captcha_system.db | SQLite 路径(Docker 用 /data/captcha.db) |
DATABASE_URL | 空 | PostgreSQL 连接串(如 postgresql://user:pass@host:5432/dbname);为空使用 SQLite |
REDIS_URL | 空 | 如 redis://127.0.0.1:6379/0 |
TRUSTED_PROXIES | 空 | 可信代理 IP/CIDR,仅命中时才信任 X-Forwarded-For |
RATE_LIMIT_GENERATE | 30 | 生成接口限流(次/分钟/IP) |
DEMO_KEY_RATE | 100 | 演示 Key 全局限流(次/分钟,所有 IP 合计) |
SLIDER_MIN_MS / SLIDER_MAX_MS / SLIDER_MIN_TRACK | 280 / 30000 / 5 | 滑动行为阈值 |
CLICK_MIN_TOTAL_MS / CLICK_MIN_GAP_MS | 600 / 120 | 点选时序阈值 |
FAIL_LOCK_THRESHOLD / FAIL_LOCK_SECONDS | 8 / 300 | 验证失败锁定(IP+Key 维度) |
LOGIN_LOCK_THRESHOLD / LOGIN_LOCK_SECONDS | 5 / 300 | 登录失败锁定(IP 维度) |
LOGIN_CAPTCHA / LOGIN_CAPTCHA_RATE | 1 / 10 | 登录验证码开关 / 获取限流(次/分钟/IP) |
鉴权说明
业务接口(验证码生成 / 校验 / 在线校验)
所有 /api/v1/captcha/* 请求需携带 API Key:
X-API-Key: <your-api-key>
# 或
Authorization: Bearer <your-api-key>
管理接口
先登录获取 JWT,再携带:
Authorization: Bearer <admin-jwt>
JWT 含角色声明(role: admin|user),普通用户仅能访问自己的 Key 与统计。
通用响应
{ "ok": true/false, "msg": "...", "data": { } }
限流 / 锁定时 HTTP 状态码为 429,body 含 retry_after(秒)。
多用户体系
角色与权限
| 角色 | 权限 |
|---|---|
管理员(内置 admin) | 管理全部 API Key(可指定归属)、创建/编辑/删除用户与用户组、设置组配额、启停用户、全量统计、查看 PASS_TOKEN_SECRET |
| 普通用户 | 登录(需验证码)后仅管理自己的 Key(受组配额限制)、查看自己的统计、不可见 PASS_TOKEN_SECRET |
用户组与配额
- 管理员创建用户组并设置
key_quota(该组用户可创建的 API Key 上限,默认 5) - 普通用户创建 Key 时自动检查配额,超限返回 403
- 组内有用户的组不可删除;删除用户会级联删除其 API Key
- 开放注册:管理后台「系统设置」开启后,登录页出现注册入口;注册用户归属默认组、普通用户权限;注册需图片验证码 + IP 限流(5 次/分钟),用户名不能与内置管理员冲突
- 管理员后台创建用户(用户名 + 初始密码,密码以 PBKDF2 哈希存储)
二次验证(TOTP)
- 绑定:登录后在「安全设置」卡片点击绑定,用 Authenticator 应用(Google / Microsoft 等)直接扫描二维码;也可手动输入密钥或粘贴 otpauth URI
- 登录:绑定后需密码 + 6 位动态码两步验证(pre_token 5 分钟一次性,输错动态码可重试)
- 解绑:需当前动态码;管理员可在用户管理中重置任意用户的 2FA
- 内置管理员同样支持绑定(密钥存 settings 表)
登录验证码
所有登录(管理员与普通用户)默认需输入图片验证码:
- 获取:
POST /api/v1/admin/captcha/generate(无需 API Key,按 IP 限流 10 次/分钟) - 登录时携带
captcha_token+captcha_code,验证码一次性使用 - 关闭:
LOGIN_CAPTCHA=0
pass_token 在线校验(普通用户接入方案)
普通用户拿不到 PASS_TOKEN_SECRET,可让接入方(如 WordPress 插件)留空密钥,改为调用服务端在线校验接口:
POST /api/v1/captcha/validate
X-API-Key: <自己的 Key>
{ "pass_token": "eyJhbGciOiJIUzI1NiIs..." }
服务端验签(用 PASS_TOKEN_SECRET)+ 一次性消费(jti 独立记录)。伪造 token / 重复使用均被拒绝。
API 接口
健康检查
GET /api/v1/health
→ {"ok": true, "ts": ..., "storage": "sqlite", "rate_limit": 30}
滑动拼图
POST /api/v1/captcha/slider/generate # 返回 token + background/puzzle 图片 + puzzle_y
POST /api/v1/captcha/slider/verify # body: {"token","offset_x","duration_ms","track":[{x,t}]}
offset_x为拼图块左上角原图像素 x(已按 pad 校正)track轨迹采样(x 原图像素、t 相对毫秒),缺失/过短/线性/超速会被判定异常- 通过后返回
pass_token(JWT,默认 60 秒有效)
点选验证码
POST /api/v1/captcha/click/generate # 返回 token + image + chars(按序点击提示)
POST /api/v1/captcha/click/verify # body: {"token","points":[{x,y}],"timings":[ms...]}
points为原图像素坐标,顺序与chars一致,容差约 28pxtimings缺失/过快/间隔过短会被判定异常
文字验证码(兼容)
POST /api/v1/captcha/text/generate
POST /api/v1/captcha/text/verify # body: {"token","code"}
连接测试(v2.2.0+)
POST /api/v1/captcha/test
→ data.pass_token 为服务端密钥签发的测试 JWT(调用方可反向验证密钥一致性)
→ data.server_secret_explicit 表示是否显式配置了 PASS_TOKEN_SECRET
在线校验(v2.4.1+)
POST /api/v1/captcha/validate # 见"多用户体系"节
管理接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/register | 用户注册(需管理面板开启;验证码 + IP 限流) |
| POST | /api/v1/admin/login | 登录(需验证码);已启用 2FA 时返回 need_2fa + pre_token |
| POST | /api/v1/admin/login/2fa | 二次验证第二步:pre_token + 6 位动态码 → 正式 JWT(一次性) |
| POST | /api/v1/admin/captcha/generate | 登录/注册验证码(无需 API Key) |
| GET/PUT | /api/v1/admin/settings | 系统设置(仅管理员):registration_enabled 开放注册开关 |
| POST | /api/v1/admin/2fa/setup | 生成 TOTP 密钥 + otpauth URI + 二维码 PNG(base64)(登录态,不落库) |
| POST | /api/v1/admin/2fa/confirm | 确认启用 2FA(密钥 + 当前动态码) |
| DELETE | /api/v1/admin/2fa | 解绑自身 2FA(需当前动态码) |
| DELETE | /api/v1/admin/users/{username}/2fa | 管理员重置指定用户的 2FA |
| GET | /api/v1/stats | 统计(管理员全量 / 普通用户仅自己的,含当前账号 twofa_enabled) |
| GET/POST | /api/v1/admin/keys | Key 列表(含 connect 配置)/ 创建(普通用户受配额限制) |
| PUT | /api/v1/admin/keys/{key} | 编辑名称/备注 |
| PUT/DELETE | /api/v1/admin/keys/{key}/enable|disable / /api/v1/admin/keys/{key} | 启停 / 删除(仅本人或管理员) |
| GET/POST | /api/v1/admin/users | 用户列表 / 创建(仅管理员) |
| PUT/DELETE | /api/v1/admin/users/{username} | 编辑(改密/改组/启停)/ 删除(级联删 Key) |
| GET/POST | /api/v1/admin/groups | 用户组列表 / 创建 |
| PUT/DELETE | /api/v1/admin/groups/{id} | 编辑配额 / 删除(组内有用户拒绝) |
WordPress 插件接入
仓库内置 wordpress/captcha-guard/ 插件(安装包 captcha-guard-1.0.10.zip):后台选择验证方式,保护登录/注册/评论/找回密码表单。
配置流程
- 后台 → API Key 卡片 → 复制插件连接配置(服务地址 / API Key / PASS_TOKEN_SECRET)
- WordPress → 设置 → Captcha Guard:粘贴三项,勾选要保护的表单
- 点击"测试连接"验证配置(服务连通 / API Key / 密钥一致性 / SDK 地址)
两种校验模式
- 本地验签(推荐,管理员):填写 PASS_TOKEN_SECRET,插件本地验证 JWT
- 在线校验(普通用户):密钥留空,插件调用
/api/v1/captcha/validate(服务端需 v2.4.1+)
登录保护开启时,插件自动拒绝 XML-RPC 认证(暴力破解高危入口);REST 评论创建同样受保护。
抗自动化
| 能力 | 说明 |
|---|---|
| 滑动轨迹 | 最短耗时、最少采样点、线性度、速度异常检测 |
| 点选时序 | 总时长、点击间隔、时间单调性 |
| 失败锁定 | 同一 IP+Key 连续失败达阈值后临时封禁(429) |
| 生成限流 | 每 IP 每分钟生成次数上限(可配置) |
| 登录防护 | 登录验证码 + IP 失败锁定 + 验证码获取限流 |
| 图像干扰 | 点选字符旋转/噪声线;拼图缺口形状随机化 |
| Token | 一次性使用 + 默认 120 秒过期(Redis 自动过期) |
无轨迹/时序数据的校验请求会被拒绝或判定异常。
安全与生产建议
- 设置
ENV=production,凭据写入.env(SECRET_KEY用openssl rand -hex 32生成并固定) - 生产环境建议单独设置
PASS_TOKEN_SECRET,缩小业务侧密钥扩散面 - 启用 Redis 实现多实例共享 Token 与限流
- 前置 Nginx/Caddy 做 HTTPS 与连接级限流;配置
TRUSTED_PROXIES后才信任X-Forwarded-For(防伪造) - 确认
DB_PATH持久化(Docker 卷/data),避免重启丢失 Key/用户数据 - 业务方校验 pass_token 时:本地验签需保管密钥;无密钥场景使用在线校验接口
- 普通用户按组分配配额;删除离职用户会级联删除其 Key
- 安装中文字体(镜像已内置 Noto CJK;宿主机可用
fonts/目录挂载)
常见问题
Q: 登录提示"验证码错误或已过期"?
点击验证码图片刷新后重试;验证码一次性使用,多次提交会失效。频繁获取会被限流(429,默认 10 次/分钟)。
Q: 普通用户配置 WordPress 插件时 PASS_TOKEN_SECRET 填什么?
留空即可——插件自动切换为在线校验模式(服务端 v2.4.1+),只需填写 API 服务地址与自己的 API Key。
Q: 汉字显示为方框?
Docker 镜像已内置 Noto CJK;裸机部署请安装 fonts-noto-cjk 或将字体放入 fonts/ 目录后重启。
Q: 服务端重启后 Key/用户丢失?
默认 DB_PATH=/tmp/... 会在清理时丢失。请将 DB_PATH 指向持久化目录(Docker 使用 /data 卷)。
Q: 滑动对不齐 / 点选点不准?
滑动使用接口返回的 puzzle_y 作为拼图块 top;点选容差 28px(原图像素),前端需按显示宽度换算坐标。
Q: 后台提示"无效或缺失 API Key"?
服务端数据库无该 Key(多半被重置或 Key 被禁用)。在后台重新创建 Key 并复制配置到接入方;确认 DB_PATH 已持久化。
Q: 演示页/文档页的 Key 会被滥用吗?
演示页与文档页使用独立的 cg-demo-* 受限 Key:不在页面展示、每次服务重启自动轮换、后台可禁用,并受全局限流(DEMO_KEY_RATE,默认 100 次/分钟、所有 IP 合计)保护,换 IP 也无法绕过。业务 Key 不会出现在页面源码中。
Q: TRUSTED_PROXIES 在 .env 里不生效?
Docker 部署时该变量由 compose 注入(TRUSTED_PROXIES=${TRUSTED_PROXIES:-}):确认 compose 已更新到最新、.env 已配置(如 TRUSTED_PROXIES=127.0.0.1),并执行 docker compose up -d 重启容器(无需重建镜像)。
Q: 构建卡在 apt-get?
使用 ./build.sh 或 docker compose up -d --build——镜像内会自动探测国内网络并切换阿里云 apt 源 / 清华 pip 源;基础镜像源由 build.sh 自动选择。