简介

动态验证码管理系统 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.ymlpostgres 服务与 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/docsAPI 文档(JSON)
默认管理员:admin / 密码取 ADMIN_PASS 环境变量(默认 admin123,生产必须修改)。普通用户由管理员在后台"用户管理"创建,或在后台「系统设置」开启开放注册后由用户自助注册。

环境变量

安全提示:设置 ENV=production 后,若 SECRET_KEY / ADMIN_PASS / DEFAULT_API_KEY 仍为默认值,应用将拒绝启动(fail-fast)。Docker 部署建议全部写入 .env

变量默认值说明
HOST / PORT0.0.0.0 / 8080监听地址与端口
ENVdevelopmentproduction 启用默认凭据 fail-fast 校验
ALLOW_INSECURE_DEFAULTS生产环境也允许默认凭据(仅调试)
SECRET_KEY随机(不持久)JWT 签名密钥,生产必须显式设置并固定
PASS_TOKEN_SECRET回退 SECRET_KEY业务 pass_token 独立签发密钥(减小扩散面)
ADMIN_USER / ADMIN_PASSadmin / admin123内置管理员凭据
DEFAULT_API_KEYdemo-api-key-captcha-2026默认 API Key
CAPTCHA_EXPIRE120验证码有效秒数
PASS_TOKEN_EXPIRE60pass_token(JWT)有效秒数
MAX_BODY_BYTES65536请求体上限(防内存耗尽)
REQUEST_TIMEOUT15单连接读取超时(秒)
MAX_CONCURRENT64最大并发连接(超限拒绝新连接)
DB_PATH/tmp/captcha_system.dbSQLite 路径(Docker 用 /data/captcha.db
DATABASE_URLPostgreSQL 连接串(如 postgresql://user:pass@host:5432/dbname);为空使用 SQLite
REDIS_URLredis://127.0.0.1:6379/0
TRUSTED_PROXIES可信代理 IP/CIDR,仅命中时才信任 X-Forwarded-For
RATE_LIMIT_GENERATE30生成接口限流(次/分钟/IP)
DEMO_KEY_RATE100演示 Key 全局限流(次/分钟,所有 IP 合计)
SLIDER_MIN_MS / SLIDER_MAX_MS / SLIDER_MIN_TRACK280 / 30000 / 5滑动行为阈值
CLICK_MIN_TOTAL_MS / CLICK_MIN_GAP_MS600 / 120点选时序阈值
FAIL_LOCK_THRESHOLD / FAIL_LOCK_SECONDS8 / 300验证失败锁定(IP+Key 维度)
LOGIN_LOCK_THRESHOLD / LOGIN_LOCK_SECONDS5 / 300登录失败锁定(IP 维度)
LOGIN_CAPTCHA / LOGIN_CAPTCHA_RATE1 / 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 一致,容差约 28px
  • timings 缺失/过快/间隔过短会被判定异常

文字验证码(兼容)

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/keysKey 列表(含 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):后台选择验证方式,保护登录/注册/评论/找回密码表单。

配置流程

  1. 后台 → API Key 卡片 → 复制插件连接配置(服务地址 / API Key / PASS_TOKEN_SECRET)
  2. WordPress → 设置 → Captcha Guard:粘贴三项,勾选要保护的表单
  3. 点击"测试连接"验证配置(服务连通 / 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 自动过期)

无轨迹/时序数据的校验请求会被拒绝或判定异常。

安全与生产建议

  1. 设置 ENV=production,凭据写入 .envSECRET_KEYopenssl rand -hex 32 生成并固定)
  2. 生产环境建议单独设置 PASS_TOKEN_SECRET,缩小业务侧密钥扩散面
  3. 启用 Redis 实现多实例共享 Token 与限流
  4. 前置 Nginx/Caddy 做 HTTPS 与连接级限流;配置 TRUSTED_PROXIES 后才信任 X-Forwarded-For(防伪造)
  5. 确认 DB_PATH 持久化(Docker 卷 /data),避免重启丢失 Key/用户数据
  6. 业务方校验 pass_token 时:本地验签需保管密钥;无密钥场景使用在线校验接口
  7. 普通用户按组分配配额;删除离职用户会级联删除其 Key
  8. 安装中文字体(镜像已内置 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.shdocker compose up -d --build——镜像内会自动探测国内网络并切换阿里云 apt 源 / 清华 pip 源;基础镜像源由 build.sh 自动选择。