boxmoe_header_banner_img

加载中

yuyu WebSafe v4.0:我写了一个带 AI 行为分析的人机验证系统


这不是一个简单的验证码,而是一套完整的人机识别方案。支持 AI 行为分析、Token 一次性绑定、图片动态水印、IP 频率限制,以及一行代码的极简接入。

项目预览



写在前面

我运营一个 WordPress 博客,流量不大,但一直有个头疼的问题:爬虫和采集器太多了。有些是搜索引擎的合法抓取,但更多的是盗站、采集、甚至 CC 攻击的脚本。

最开始我用了 Google reCAPTCHA,但国内访问太慢。后来试过极验,收费不便宜。也想过自己写一个简单的图片验证码,但那只能防最基础的脚本,稍微懂点技术的就能绕过去。

于是我决定自己写一个。

从最初的简单图片点选,到后来加入算术题、行为采集,再到这次 v4.0 完全重构,yuyu WebSafe 已经变成一个真正能对抗自动化脚本和爬虫的人机验证服务。最重要的是,它完全免费,任何网站都可以用一行代码接入。

这篇文章会详细介绍 v4.0 的所有更新,以及如何接入。如果你也在被机器人困扰,希望能给你一些参考。


一、旧版本的问题

在开始介绍 v4.0 之前,先说说旧版本有哪些让我不满意的地方:

  1. Token 可以重复使用

旧版本生成 token 后没有做防重放处理。这意味着如果 token 在传输过程中被截获(比如通过不安全的网络),攻击者可以拿着这个 token 反复使用。更糟糕的是,同一个 token 可以在不同 IP 下使用,完全无法追溯。

  1. 图片直接暴露路径

所有角色图片都直接放在 images/ 目录下,路径是公开的。攻击者可以写个脚本把所有图片下载下来,然后建立本地特征库。这样一来,角色验证就形同虚设了——机器可以直接比对本地图片,根本不需要识别。

  1. 算术题答案在前端生成

这是最离谱的问题。之前为了省事,算术题的题目和答案都在前端生成,然后通过 POST 提交给后端验证。稍微懂点 JavaScript 的人,打开控制台就能看到正确答案,直接绕过验证。

  1. 没有 IP 绑定和频率限制

同一个 IP 可以在短时间内发起无数次验证请求,没有任何限制。这意味着攻击者可以暴力尝试,或者通过大量请求消耗服务器资源。

这些问题堆积在一起,让我意识到:如果只是“能用”,那这个系统其实跟没有一样。v4.0 的目标很明确——让验证真正“防得住”。


二、v4.0 整体架构

在开始写代码之前,我先把整体架构理清楚了。

┌─────────────────────────────────────────────────────────────┐
│                      接入方网站                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  <script src="https://ovo.yuyu09.com/verify-client.js"> │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   验证服务 (ovo.yuyu09.com)                  │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  index.html      │  验证页面 (Vue 3 + Tailwind)      │ │
│  │  api.php         │  后端核心 (出题/验证/频率限制)    │ │
│  │  safe.php        │  安全防护 (防直接请求)            │ │
│  │  yuyu.js         │  静态站防御脚本                   │ │
│  │  verify-client.js│  通用接入脚本                     │ │
│  │  how-to.html     │  接入指南页面                     │ │
│  │  images/         │  角色图片 (动态加水印)            │ │
│  │  data/           │  运行时数据 (Token记录/频率计数)  │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

整个系统分为三层:

· 接入层:第三方网站通过引入一个 JS 文件即可接入
· 验证层:独立的验证服务,提供人机验证界面和 API
· 数据层:存储 Token、频率计数、域名白名单等运行时数据


三、安全加固详解

3.1 Token 一次性 + IP 绑定

这是 v4.0 最重要的安全改进。

Token 结构(JWT):

{
  "iss": "yuyu",
  "iat": 1740965310,
  "exp": 1741051710,
  "jti": "a006630316eace36b35d2d359fd3d356",
  "ip": "f885cbd1c3cc7ce219683c9615046d5691bf5cf877af58fe357460a2140003bb",
  "ua": "bb752c50a00839b3879be37474c39b38"
}

每个字段都有具体作用:

· iss:签发者标识
· iat:签发时间
· exp:过期时间(1小时后自动失效)
· jti:JWT ID,唯一标识,用于防重放
· ip:用户 IP 的 SHA-256 哈希值
· ua:User-Agent 的 MD5 哈希值

验证流程:

  1. 用户访问接入网站,被跳转到验证页
  2. 验证页调用 api.php?action=question 获取题目
  3. 用户完成验证后,调用 api.php?action=verify 提交答案
  4. 验证通过后,生成包含 IP 和 UA 哈希的 JWT Token
  5. Token 返回给前端,并携带跳转回原网站
  6. 原网站的 verify-client.js 收到 Token 后设置 Cookie
  7. Token 被记录到 data/jti.json,同一 Token 再次使用会被拒绝

为什么这样做:

· Token 绑定 IP,即使被截获也无法在其他网络使用
· Token 绑定 UA,即使在同一 IP 下更换浏览器也无法使用
· 一次性机制确保每个 Token 只能用一次,防止重放攻击
· 1小时过期时间,避免 Token 长期有效带来的风险

3.2 图片动态水印

这是针对 AI 图像识别的防御手段。

旧版本的图片直接暴露路径,攻击者可以写脚本把所有图片下载下来,建立特征库。v4.0 的图片不再直接访问,而是通过 api.php?action=img&id=xxx 动态输出。

每次输出时,会通过 GD 库在图片上叠加:

· 50 个随机噪点像素:位置和颜色随机
· 4 条半透明线条:位置、角度、透明度随机

// 添加随机噪点
for ($i = 0; $i < 50; $i++) {
    $x = rand(0, $width);
    $y = rand(0, $height);
    $color = imagecolorallocate($img, rand(0, 255), rand(0, 255), rand(0, 255));
    imagesetpixel($img, $x, $y, $color);
}

// 添加随机线条
for ($i = 0; $i < 4; $i++) {
    $color = imagecolorallocatealpha($img, rand(0, 255), rand(0, 255), rand(0, 255), rand(0, 50));
    imageline($img, rand(0, $width), rand(0, $height), rand(0, $width), rand(0, $height), $color);
}

效果:

· 水印位置和透明度随机,人眼看不出区别
· AI 识别时,这些噪点和线条会被当成图像特征的一部分
· 同一张图片每次请求的水印都不同,AI 无法建立稳定的特征库

3.3 行为分析

这是区分真人和脚本的核心。

前端 yuyu.js 会持续采集用户行为数据:

· 鼠标移动轨迹(坐标、时间戳)
· 点击位置和时机
· 键盘敲击频率
· 触摸屏操作(移动端)
· 页面滚动行为

提交验证时,这些数据会一起发送到后端:

// 行为数据采集示例
const behavior = {
    startTime: Date.now() - pageLoadTime,      // 从页面加载到开始验证的时间
    reactionMs: verifyClickTime - startTime,   // 点击验证按钮的反应时间
    mousePoints: trajectory.points.length,     // 鼠标轨迹点数量
    mouseDist: trajectory.totalDistance,       // 鼠标移动总距离
    webdriver: navigator.webdriver,            // 是否是无头浏览器
    touchEvents: touchCount,                   // 触摸事件次数
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight
};

服务端逻辑:

检测项 阈值 判定
反应时间 < 300ms 脚本(真人不可能这么快) 鼠标轨迹点 < 3 个(桌面端) 脚本(真人移动一定有轨迹) 鼠标移动距离 < 5px(桌面端) 脚本(真人不会原地点击) navigator.webdriver true 脚本(无头浏览器特征) 移动端触摸事件 0 次 脚本(移动端必定有触摸)

为什么这样设计:

· 真人的反应时间通常在 300ms 以上
· 真人点击前鼠标一定会移动,会产生轨迹点
· 无头浏览器(如 Puppeteer)会暴露 webdriver 属性
· 移动端用户必然有触摸操作

3.4 IP + Session 双重频率限制

这是为了防止暴力请求和 CC 攻击。

频率限制分为两个维度:

Session 维度(基于 PHP Session):

· 每分钟最多 60 次请求
· 超限后返回 429 状态码

IP 维度(基于文件缓存):

· 每分钟最多 30 次请求
· 超限后返回429 状态码

// 频率限制核心逻辑
function rateLimit($key, $limit, $window) {
    $file = DATA_PATH . "/ip_{$key}.json";
    $now = time();
    $data = file_exists($file) ? json_decode(file_get_contents($file), true) : [];
    
    // 清理过期记录
    $data = array_filter($data, function($ts) use ($now, $window) {
        return $ts > $now - $window;
    });
    
    // 检查是否超限
    if (count($data) >= $limit) {
        return false;
    }
    
    // 记录本次请求
    $data[] = $now;
    file_put_contents($file, json_encode($data));
    return true;
}

两个维度独立计算,触发任意一个都会拒绝请求,并返回剩余锁定时间。

3.5 CSP 安全头

所有 JSON 接口响应都会添加:

Content-Security-Policy: default-src 'none'

这个头部的作用是防止 XSS 攻击。即使攻击者找到了注入点,也无法通过接口响应执行恶意脚本。


四、UI 重构:Vue 3 + Tailwind CSS

旧版本的 UI 是用原生 JS 写的,虽然能用,但代码维护起来很痛苦。v4.0 完全重写了前端,采用了现代技术栈。

技术选型

· Vue 3:响应式状态管理,代码更清晰
· Tailwind CSS:原子化样式,不用写一行 CSS
· Lucide Icons:统一的 SVG 图标库

页面结构

验证页有两个模式,通过 Tab 切换:

角色验证模式:

· 2×2 图片网格
· 题目显示需要选择的角色名
· 点击图片即选中,再次点击可切换
· 选中后显示绿色勾选标记

算术验证模式:

· 支持加法、减法、乘法
· 数字精确到一位小数(如 3.2、7.5)
· 答案精确到两位小数
· 干扰选项在正确答案附近随机分布
· 允许 ±0.015 的误差(防止浮点精度问题)

验证流程

  1. 页面加载时,调用 api.php?action=question 获取题目
  2. 根据当前模式渲染对应的 UI
  3. 用户选择答案后,点击「验证身份」
  4. 前端收集行为数据,连同答案一起 POST 到 api.php?action=verify
  5. 服务端校验:

· 检查行为数据是否异常
· 检查频率限制
· 验证答案是否正确
· 记录失败次数,超限则锁定

  1. 验证通过后,生成 Token 并跳转回原网站

失败处理

· 验证失败时显示剩余尝试次数
· 失败 5 次后锁定 15 分钟
· 锁定时显示倒计时提示


五、接入方式(完整版)

这是 v4.0 最重要的改进之一:接入方式从“需要修改多处配置”变成了“一行代码”。

5.1 核心脚本

接入只需要在网站底部添加一行代码:

<script src="https://ovo.yuyu09.com/verify-client.js"></script>

这个脚本会自动处理:

· 检查用户是否已验证(通过 Cookie 或 localStorage)
· 未验证则跳转到验证页,并保存当前页面地址
· 验证成功后设置 Cookie 并自动刷新

5.2 分平台接入说明

不同平台的添加位置略有不同,下面是详细指南:

WordPress

推荐方法:安装「Insert Headers and Footers」插件,在「页脚脚本」里粘贴代码。

手动方法:编辑主题文件 footer.php,在 标签前添加代码。

主题目录/
├── footer.php    ← 在这个文件的 </body> 前添加
├── header.php
└── ...

Hexo

找到主题的布局文件,通常在 themes/你的主题/layout/_partial/footer.ejs 或 .njk,在 前添加代码,重新部署即可。

themes/
└── your-theme/
    └── layout/
        └── _partial/
            └── footer.ejs   ← 在这里添加

Astro

Astro 的布局文件通常在 src/layouts/ 目录下,在 前添加代码。

src/
└── layouts/
    └── BaseLayout.astro   ← 在这里添加

纯 HTML

直接在每个页面的 标签前粘贴代码。如果有公共模板(如 header/footer),加在模板文件里更方便。

<!DOCTYPE html>
<html>
<head>...</head>
<body>
  ... 你的内容 ...
  
  <!-- 在这里添加 -->
  <script src="https://ovo.yuyu09.com/verify-client.js"></script>
</body>
</html>

Vue / React

把代码放在 public/index.html 的 前,这样所有页面都会生效。

<!-- public/index.html -->
<body>
  <div id="app"></div>
  <script src="https://ovo.yuyu09.com/verify-client.js"></script>
</body>

通用网站(任何框架)

把代码复制到网站模板的底部(通常在 前),保存后清理缓存测试。

5.3 只验证首页

有些网站可能只想验证首页,文章页和分类页不验证。可以通过 data-only-root 属性实现:

<script src="https://ovo.yuyu09.com/verify-client.js" data-only-root="true"></script>

添加这个属性后,只有访问根路径(/)或 index.php 时才会跳转验证,其他页面直接放行。

5.4 自定义有效期

默认验证有效期为 1 天。如果需要修改,可以在代码中添加 data-cookie-days 属性:

<script src="https://ovo.yuyu09.com/verify-client.js" data-cookie-days="7"></script>

这样用户验证一次后,7 天内不需要重复验证。

5.5 调试模式

如果遇到问题,可以开启调试模式,在控制台查看日志:

<script src="https://ovo.yuyu09.com/verify-client.js" data-debug="true"></script>

六、傻瓜式接入页面

为了让大家更方便地接入,我还做了一个一键接入指南页面:

👉 https://ovo.yuyu09.com/how-to.html
接入文档

打开之后,只需要三步:

第一步:选择你的网站类型

页面提供了 5 个选项:

· 🌐 通用网站
· 📝 WordPress
· ⚡ Hexo / Astro
· 📄 纯 HTML
· 🟢 Vue / React

第二步:复制代码

根据你选择的类型,页面会自动显示对应的代码和接入说明。点击「复制代码」按钮,一键复制。

第三步:粘贴到网站底部

按照页面提示,把代码粘贴到网站模板的 前,保存后清理缓存测试。

常见问题

页面底部还列出了常见问题:

· Q:验证有效期多久?
A:默认 1 天,用户验证后 1 天内不需要重复验证。
· Q:只验证首页可以吗?
A:可以!在代码后面加 data-only-root=”true” 即可。
· Q:需要付费吗?
A:完全免费,yuyu 的博客公益提供。
· Q:会影响网站速度吗?
A:验证脚本仅 3KB,且异步加载,基本无影响。
· Q:移动端兼容吗?
A:完全兼容,且移动端会豁免鼠标轨迹检测,体验更好。


七、域名审核机制

为了防止滥用,v4.0 新增了域名申请审核机制。

申请流程

接入方需要先提交域名申请:

fetch('https://ovo.yuyu09.com/api.php?action=register_redirect', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    domain: 'example.com', 
    desc: '我的个人博客' 
  })
})

申请提交后,会进入待审核状态。未审核的域名也可以临时使用,但会有更严格的频率限制。

管理员审核

访问管理员页面:

https://ovo.yuyu09.com/api.php?action=admin

管理员可以:

· 查看所有申请列表
· 通过/拒绝申请
· 撤销已通过的域名
· 设置域名配额(每天请求次数上限)

内置白名单

以下域名无需申请,自动通过:

· yuyu09.com
· www.yuyu09.com
· ovo.yuyu09.com
· momo.yuyu09.com


八、文件结构

完整的文件结构如下:

ovo.yuyu09.com/
├── api.php              # 后端核心(15KB)
│                        # - 出题接口 (action=question)
│                        # - 验证接口 (action=verify)
│                        # - 图片接口 (action=img)
│                        # - 域名注册接口 (action=register_redirect)
│                        # - 管理员接口 (action=admin)
│
├── index.html           # 验证页面(Vue 3 + Tailwind,约 25KB)
│                        # - 角色验证模式
│                        # - 算术验证模式
│                        # - 行为数据采集
│
├── safe.php             # 安全防护脚本(4KB)
│                        # - 防直接请求
│                        # - 频率限制前置
│
├── yuyu.js              # 静态站防御脚本(3.5KB)
│                        # - 行为数据采集
│                        # - 环境指纹检测
│
├── verify-client.js     # 通用接入脚本(3KB)
│                        # - Token 自动处理
│                        # - Cookie 管理
│                        # - 跳转控制
│
├── how-to.html          # 接入指南页面(约 15KB)
│                        # - 平台选择
│                        # - 一键复制代码
│                        # - 常见问题
│
├── images/              # 角色图片目录
│   ├── .htaccess        # 禁止直接访问
│   └── *.webp           # 角色图片(约 300KB)
│
└── data/                # 运行时数据(自动创建,需 755 权限)
    ├── redirects.json   # 域名白名单数据库
    ├── jti.json         # Token 已用列表(防重放)
    ├── ip_*.json        # IP 频率限制计数
    └── session_*.json   # Session 频率限制计数

九、部署要求

如果你也想自己部署一套,需要满足以下环境要求:

项目 要求
PHP 版本 = 7.4
扩展 GD(图片处理)、JSON、Session
目录权限 data/ 目录需 755 权限
域名 需要独立的子域名(如 ovo.yuyu09.com)

配置修改

在 api.php 顶部,需要修改以下配置:

// 修改为你的密钥(越长越好)
define('SECRET_KEY', '你的随机密钥');

// 管理员密码哈希(用 password_hash() 生成)
define('ADMIN_PASS_HASH', '$2y$10$...');

// 验证页地址
define('VERIFY_URL', 'https://ovo.yuyu09.com');

// 数据目录
define('DATA_PATH', __DIR__ . '/data');

十、几个想说的

这个项目从最初只是给自己的 WordPress 博客加一个验证,到现在做成一个相对完整的服务,中间改了很多版。

v4.0 是我觉得真正“可靠”的一个版本:

· Token 一次性 + IP 绑定,不怕被盗用
· 图片加水印,增加 AI 识别成本
· 行为分析,拒绝纯脚本
· 频率限制,防止被刷
· 域名审核,避免滥用
· 接入指南,小白也能用

如果你也在运营一个网站,被机器人骚扰、被爬虫抓内容、或者只是单纯想加一层保护,可以试试接入这个验证。

完全免费,我也没有收费的打算。


十一、接入方式(再次总结)

  1. 选择你的平台,在网站底部添加对应代码:
<script src="https://ovo.yuyu09.com/verify-client.js"></script>
   
  1. 用户第一次访问时会自动跳转到 ovo.yuyu09.com 进行验证
  2. 验证通过后自动跳回原网站,有效期默认 1 天
  3. 可选配置:

· 只验证首页:加 data-only-root=”true”
· 自定义有效期:加 data-cookie-days=”7″
· 调试模式:加 data-debug=”true”

或者直接打开 接入指南,按步骤操作。


写在最后

项目开源吗?暂时还没想好,但服务会一直免费提供。

如果你在使用中遇到任何问题,欢迎在评论区留言,或者直接联系我(联系方式在博客关于页面)。

另外,如果你也想自己搭一套类似的验证系统,api.php 和 index.html 的代码我整理得比较清晰,可以当作参考。

最后,感谢你看到这里。


yuyu
2026.03.21

上一次更新已经跑远了✨ 计算中...
(‾◡◝) 本内容里的一些消息,可能已经跟不上时间啦~
感谢您的支持
微信赞赏

微信扫一扫

支付宝赞赏

支付宝扫一扫



评论(0)

查看评论列表

暂无评论


发表评论

北京时间 (Asia/Shanghai)

定位中...
🌤️
--°C
加载中...
体感: --°C
湿度: --%
重要的日子2026年3月20日
重要的日子即将来临。

博客统计

  • 247 点击次数
2026 年 3 月
 12
3456789
10111213141516
17181920212223
2425262728  

已阻挡的垃圾评论

后退
前进
刷新
复制
粘贴
全选
删除
返回首页

💿 音乐控制窗口

🎼 歌词

🪗 歌曲信息

封面

🎚️ 播放控制

🎶 播放进度

00:00 00:00

🔊 音量控制

100%

📋 歌单

0%
目录
顶部
底部
📖 文章导读