From 4be7c19798454ae88786fc8f77b6312b3f39fe11 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 6 Jun 2026 12:07:39 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20POW=20=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/config.ts | 5 +- docs/design/architecture.md | 2 +- docs/design/login-captcha.md | 124 ++++++++++++++++++++ docs/plan/20260606-login-cap-integration.md | 92 +++++++++++++++ 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 docs/design/login-captcha.md create mode 100644 docs/plan/20260606-login-cap-integration.md diff --git a/docs/config.ts b/docs/config.ts index cbdb50cf..84fb4cc9 100644 --- a/docs/config.ts +++ b/docs/config.ts @@ -1,4 +1,4 @@ -import { defineAdditionalConfig, type DefaultTheme } from 'vitepress' +import {type DefaultTheme, defineAdditionalConfig} from 'vitepress' export default defineAdditionalConfig({ description: @@ -129,7 +129,8 @@ function sidebarDesign(): DefaultTheme.SidebarItem[] { { text: '内网穿透隧道设计', link: 'tunnel-design' }, { text: 'WAF 设计', link: 'waf-design' }, { text: 'Pages 静态托管设计', link: 'pages-design' }, - { text: 'Uptime Kuma 监控同步设计', link: 'kuma-design' } + { text: 'Uptime Kuma 监控同步设计', link: 'kuma-design' }, + { text: '登录验证码设计', link: 'login-captcha' } ] } ] diff --git a/docs/design/architecture.md b/docs/design/architecture.md index e5b66829..00c30203 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -65,7 +65,7 @@ OpenResty (Agent, TLS/WAF) | 组件 | 职责 | 详细设计参考 | | --------------- | ---------------------------------------------------------------------- | ------------ | -| **Server** | 管理端 UI/API、控制面状态持久化、配置编译渲染、发布版本控制、Pages 部署包存储与 Uptime Kuma 监控同步 | [Agent 与发布模型](./agent-design.md) / [Uptime Kuma 监控同步设计](./kuma-design.md) | +| **Server** | 管理端 UI/API、控制面状态持久化、配置编译渲染、发布版本控制、Pages 部署包存储、Uptime Kuma 监控同步与登录验证码防护 | [Agent 与发布模型](./agent-design.md) / [Uptime Kuma 监控同步设计](./kuma-design.md) / [登录验证码设计](./login-captcha.md) | | **Agent** | 周期心跳与 WS 同步、静态资源包拉取与解压、OpenResty 配置写入/校验/重载与自愈 | [Agent 与发布模型](./agent-design.md) | | **OpenResty** | 接收真实流量,执行 WAF 过滤、PoW 防护、Basic Auth 认证与静态/反代服务 | [WAF 设计](./waf-design.md) / [Pages 设计](./pages-design.md) | | **Relay** | 部署于边缘节点,管理 `frps` 守护进程生命周期,接受心跳派发的穿透中继配置 | [内网穿透设计](./tunnel-design.md) | diff --git a/docs/design/login-captcha.md b/docs/design/login-captcha.md new file mode 100644 index 00000000..740d675f --- /dev/null +++ b/docs/design/login-captcha.md @@ -0,0 +1,124 @@ +# 登录验证码设计 (Login CAPTCHA Integration) + +本文档阐述在 OpenFlare 控制面中引入基于 Proof-of-Work (PoW) 与无感浏览器指纹特征的开源 CAPTCHA 方案 —— Cap,以防止对登录 API 进行暴力破解与爬虫撞库攻击的设计。 + +--- + +## 1. 业务背景与产品范围 + +### 背景与痛点 +根据我们的系统安全分析,OpenFlare 的登录端点 `/api/user/login` 虽然配置了基于 IP 的限流限制,但由于缺少用户维度的防护机制,攻击者可使用代理池绕过 IP 限制对高权限账户(如 `root`)实施撞库和暴力破解。同时,对于系统登录页面,标准的视觉验证码对用户体验和无障碍不够友好。 + +### 产品范围与技术选型 +* **技术选型**:Cap (Proof-of-Work 驱动的无感无图像验证码解决方案)。 + - **核心原理**:客户端(Widget/网页)从服务器获取工作量证明 (PoW) 的难题,使用浏览器后台计算求解并将答案回传。服务器验证答案的正确性,完成人机识别。 + - **优势**:无感、无图像验证、不依赖任何外部第三方 API 节点(私密)、包极小。 +* **接入范围**:控制面 Server 登录 API(`/api/user/login`)以及前端登录页面。 +* **配置粒度**:支持管理员通过控制台 Option 表随时开启/关闭验证码(`CapLoginEnabled`)。 + +--- + +## 2. 系统架构与交互时序 + +### 2.1 模块分工 +1. **Frontend (前端)**: + * 在登录页面引入 `cap-widget`(React 19 自定义元素)。 + * 提交表单时,伴随提交由 Widget 求解出并得到的 `cap-token`。 +2. **Server (控制面后端)**: + * 暴露 `POST /api/cap/challenge` 接口,为客户端分发 PoW 难题和签名的 JWT Token。 + * 暴露 `POST /api/cap/redeem` 接口,校验客户端提交的 PoW 解答并核发带有失效时间的登录凭证(Redeem Token)。 + * 将 Redeem Token 与对应过期时间保存在内存缓存/Redis 缓存中。 + * 在 `POST /api/user/login` 接口中,若启用了验证码保护,先校验并消耗(单次失效)对应的 `cap-token`。 + +### 2.2 验证流时序图 +```mermaid +sequenceDiagram + autonumber + actor User as 用户 + participant Browser as 浏览器 (前端 Web) + participant Server as OpenFlare Server (后端) + participant Cache as 内存/Redis 缓存 + + User->>Browser: 打开登录页面 + Browser->>Server: POST /api/cap/challenge (获取难题) + Server->>Browser: 返回 {challenge, token, expires} (JWT 格式) + Note over Browser: Widget 在后台(WASM/Worker)执行 PoW 难题计算 + Browser->>Server: POST /api/cap/redeem (提交 solutions + token) + alt 校验 PoW 解答通过 + Server->>Cache: 存储 Redeem Token (tokenKey:expires) + Server->>Browser: 返回 {success: true, token} (即 cap-token) + else 校验失败 + Server->>Browser: 返回 {success: false, reason} + end + User->>Browser: 输入账号密码,点击登录 + Browser->>Server: POST /api/user/login (在 HTTP 请求头中携带 X-Cap-Token) + alt CapLoginEnabled = true + Server->>Server: Middleware (CapAuth) 校验并消费 X-Cap-Token + alt token 合法且未过期且未被消费 + Server->>Server: c.Next() -> 执行常规登录逻辑 (密码 Bcrypt 校验) + Server->>Browser: 返回登录成功 (JWT session) + else token 无效或已被消费 + Server->>Browser: 拦截并返回验证码错误 (401 Unauthorized) + end + else CapLoginEnabled = false + Server->>Server: c.Next() -> 执行常规登录逻辑 + end +``` + +--- + +## 3. 核心接口与数据模型 + +### 3.1 接口定义 + +#### 1. 获取难题 (GET/POST /api/cap/challenge) +* **请求方式**:`POST` +* **接口权限**:公开 +* **响应负载**: + ```json + { + "challenge": { + "c": 50, + "s": 32, + "d": 4 + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjIjo1MCwicyI6MzIsImQiOjQsImV4cCI6MTcxNzY2MDgwMCwiaWF0IjoxNzE3NjYwMjAwLCJuIjoiMGExYjJjM2Q0ZTVmNiJ9.signature", + "expires": 1717660800000 + } + ``` + +#### 2. 核销难题 (POST /api/cap/redeem) +* **请求方式**:`POST` +* **请求负载**: + ```json + { + "token": "challenge_jwt_token_here", + "solutions": [12345, 67890, 54321] + } + ``` +* **响应负载 (成功)**: + ```json + { + "success": true, + "token": "random_id:ver_token", + "expires": 1717661000000 + } + ``` + +#### 3. 登录接口 (POST /api/user/login) +* **请求负载保持不变**: + ```json + { + "username": "root", + "password": "your_password" + } + ``` +* **验证码载体**:放置于 HTTP Request Header `X-Cap-Token` 中。 + +--- + +## 4. 重放攻击防护与安全性权衡 +1. **JWT 临时状态绑定**:难题在生成时就被签入 JWT payload,包含过期时间限制(10 分钟)。 +2. **Replay 拦截(Nonce 消耗)**:当客户端调用 `/redeem` 提交解答时,后端在缓存中标记该 JWT Signature 已使用。重复提交相同的解密包将返回 `already_redeemed`。 +3. **Redeem 一次性核销(单次失效)**:当客户端登录并提交 `cap-token` 时,后端在检验到合法性后立即从缓存中删除该 Key,防止黑客提取历史正确的 `cap-token` 进行重放登录。 +4. **验证机制无感化**:通过调整 `c (难题数)=50`,`d (难度)=4`,普通用户在桌面端和移动端只需 0.5 秒至 1.5 秒即可静默解出,极大地兼顾了用户体验和反爬效果。 diff --git a/docs/plan/20260606-login-cap-integration.md b/docs/plan/20260606-login-cap-integration.md new file mode 100644 index 00000000..163d337c --- /dev/null +++ b/docs/plan/20260606-login-cap-integration.md @@ -0,0 +1,92 @@ +# 登录集成 Cap 验证码实现计划 + +本计划规定了在 OpenFlare 系统的登录流程中集成 Cap(基于 Proof-of-Work 和无感浏览器检测的验证码)的具体开发步骤。 + +--- + +## 1. 目标与背景 (Goal & Context) + +### 需求背景 +为解决安全分析中识别到的“登录接口缺少防暴力破解/撞库逻辑”这一安全风险,我们需要在登录接口中集成 Cap 验证码服务。通过让客户端(爬虫/浏览器)在登录前必须求解一个 PoW 工作量难题并核销,显著提高恶意爬虫爆破的计算成本,从根本上防止针对登录接口的恶意爆破。 + +### 开发范围 +1. **后端验证服务**:在 Server 端移植 `capjs-core` 的 PoW 校验算法(包括 FNV-1a、自定义 PRNG、SHA-256 检验、JWT 难题派发与核销缓存组件)。 +2. **公开路由映射**: + * `POST /api/cap/challenge` (分发难题) + * `POST /api/cap/redeem` (核销难题并核发 `cap-token`) +3. **控制开关**:增加全局选项 `CapLoginEnabled`,管理员可动态启停。 +4. **前端交互接入**:在登录页面引入 `cap-widget` 自定义组件,并在提交登录请求时附带 `cap_token`。 + +--- + +## 2. 设计与决策 (Design & Decisions) + +### 核心对象与数据模型 +本方案不涉及复杂数据库结构重构,但需要: +1. 在 `options` 表中保存 `CapLoginEnabled` (true/false) 选项, 默认为 True, 设置路径在 设置->系统设置->登录与注册开关。 +2. 建立一个全局的、线程安全的内存验证码核销存储/核销缓存,具备过期清理功能,用于存放核销的 `cap-token` 以及消费过的 JWT 难题 Nonce(Signature),支持 Redis 与本地内存模式。 + +### API 与鉴权设计 +1. **`POST /api/cap/challenge`**:公开接口。 +2. **`POST /api/cap/redeem`**:公开接口。 +3. **`POST /api/user/login`**:接受可选/必选的 `cap_token` 参数。 + +### 算法移植 (Proof-of-Work Go 实现) +* FNV-1a 状态机复现。 +* 伪随机数生成器 (PRNG) 与 `strings.HasPrefix(sha256Hex, target)` 校验。 + +--- + +## 3. 具体修改文件清单 (Proposed Changes) + +### 后端 Server +* #### [MODIFY] [constants.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/common/constants.go) + * 增加 `CapLoginEnabled` 全局常量/变量,默认 `true`。 +* #### [MODIFY] [option.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/model/option.go) + * 在 `InitOptionMap` 和 `updateOptionMap` 中添加 `CapLoginEnabled` 的支持。 +* #### [NEW] [prng.go (utils/cap)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/utils/cap/prng.go) + * 职责:实现 FNV-1a、FNV-1a resume 及 XORShift-based 自定义 PRNG 伪随机数算法。 +* #### [NEW] [cap.go (utils/cap)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/utils/cap/cap.go) + * 职责:实现无状态 PoW 难题生成、验证及 JWT 校验。 +* #### [NEW] [store.go (utils/cap)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/utils/cap/store.go) + * 职责:定义 `Store` 接口并提供默认的高性能、线程安全的内存 TTL 缓存核销存储实现。 +* #### [NEW] [manager.go (utils/cap)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/utils/cap/manager.go) + * 职责:封装验证码的核心逻辑,暴露出 `Generate`、`Redeem` 与 `VerifyToken` 高阶 API。 +* #### [NEW] [middleware.go (utils/cap)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/utils/cap/middleware.go) + * 职责:实现通用的 Gin 中间件 `VerifyMiddleware`。其不依赖任何 OpenFlare 业务代码,完全通过构造注入。 +* #### [NEW] [cap.go (service)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/service/cap.go) + * 职责:适配器服务,将 OpenFlare 的全局参数(如 `JWTSecret`、`CapLoginEnabled`、`RDB`)注入并实例化全局的 `CapManager` 实例。 +* #### [NEW] [cap.go (middleware)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/middleware/cap.go) + * 职责:极简的适配器中间件,直接调用并返回 `service.CapManager.VerifyMiddleware(scope)`。 +* #### [NEW] [cap.go (controller)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/controller/cap.go) + * 职责:实现 `GetCapChallenge` 和 `RedeemCapChallenge` 控制器。 +* #### [MODIFY] [api-router.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/router/api-router.go) + * 职责:挂载 `/api/cap/challenge` 和 `/api/cap/redeem` 路由,并在 `/api/user/login` 上应用 `middleware.CapAuth("login")`。 +* #### [MODIFY] [user.go (controller)](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/controller/user.go) + * 无需修改:登录控制器和入参结构体保持完全无侵入。 +* #### [MODIFY] [misc.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/controller/misc.go) + * 职责:在 `GetStatus` 中返回 `cap_login_enabled` 开关状态。 + +### 前端 Web +* #### [MODIFY] [public-status.ts](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/web/types/public-status.ts) + * 添加 `cap_login_enabled: boolean` 字段。 +* #### [MODIFY] [auth.ts](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/web/types/auth.ts) + * 在 `LoginPayload` 中添加可选的 `cap_token?: string` 属性。 +* #### [MODIFY] [login-form.tsx](file:///Users/ryan/DEV/Go/OpenFlare/openflare-server/web/features/auth/components/login-form.tsx) + * 动态载入 `cap-widget`(脚本 CDN:`https://cdn.jsdelivr.net/npm/cap-widget`)。 + * 若后台返回 `cap_login_enabled === true`,则渲染 `` 组件。 + * 在表单提交时,将 `cap-token` 塞入 `loginMutation` 的 Payload 中提交。 + +--- + +## 4. 验证计划 (Verification Plan) + +### 自动化单元测试 +* 针对 Go 中的 PoW 核心算法,编写单测 `openflare-server/service/cap_test.go`。 +* 运行单测命令:`go test -v ./openflare-server/service/...` + +### 手动功能与防暴力破解验证 +1. 打开控制台选项开启 `CapLoginEnabled`。 +2. 访问登录页面,观察人机验证组件静默加载并完成 PoW 计算,输入正确账户成功登录。 +3. 使用 `curl` 模拟恶意爬虫不携带或携带错误的 `cap_token` 对登录 API 发起 POST 请求,预期被拦截并返回“验证码错误”。 +4. 使用已被核销的同一 `cap_token` 二次请求登录,验证防重放失效机制。