Files
zclaw_openfang/wiki/security.md
iven 5d88d129d1 docs(wiki): Phase B+C完成 — middleware/saas/security/memory 5节模板重构
- middleware.md: 集成契约+3不变量+执行流 (157→136行)
- saas.md: 移除安全重复→引用security.md+Token Pool算法 (231→173行)
- security.md: 吸收saas认证内容成为安全唯一真相源 (158→199行)
- memory.md: 最大压缩363→147行+Hermes洞察提炼+4不变量

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:42:24 +08:00

200 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 安全体系
updated: 2026-04-22
status: active
tags: [module, security, auth, encryption]
---
# 安全体系
> 从 [[index]] 导航。关联模块: [[saas]] [[routing]] [[middleware]]
## 设计决策
**核心原则: 多层防御,深度安全。**
| 决策 | 为什么 |
|------|--------|
| JWT + HttpOnly Cookie 双通道 | Tauri 桌面端用 OS keyring 存 JWT浏览器用 HttpOnly Cookie 防 XSS 窃取,双环境统一认证 |
| password_version (pwv) 失效 | 修改密码后自动使所有已签发 JWT 失效,无需 token 黑名单O(1) 验证 |
| TOTP AES-256-GCM 加密 | TOTP 共享密钥不能明文存储,随机 Nonce 防重放,生产环境强制独立密钥 |
| IP 级限流 + 持久化 | 防暴力破解login 5/min、防刷注册3/hour持久化到 PostgreSQL 避免重启丢失 |
| CORS 白名单强制 | 生产环境 `cors_origins` 缺失直接拒绝启动,不允许 `*` 通配 |
完整审计报告: `docs/features/SECURITY_PENETRATION_TEST_V1.md`
## 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `crates/zclaw-saas/src/auth/handlers.rs` | 认证端点: 登录/注册/刷新/TOTP/密码修改 |
| `crates/zclaw-saas/src/auth/totp.rs` | TOTP 2FA: QR 生成 + 验证 + AES-256-GCM 加密 |
| `crates/zclaw-saas/src/middleware.rs` | HTTP 中间件栈 (10 层): 认证/限流/配额/CORS |
| `crates/zclaw-saas/src/relay/key_pool.rs` | Token Pool: Key 加密存储 + RPM/TPM 轮换 |
| `desktop/src-tauri/src/secure_storage.rs` | OS Keyring: Win DPAPI / macOS Keychain / Linux Secret |
| `desktop/src-tauri/src/memory/crypto.rs` | 本地记忆加密: AES-256-GCM (可选) |
### 认证数据流
```
用户登录 (POST /api/v1/auth/login)
→ Argon2id + OsRng 验证密码
→ 账户锁定检查 (5 次失败 → 15 分钟锁定)
→ 签发 JWT (Claims: user_id, role, pwv)
→ set_auth_cookies():
zclaw_access_token (path:/api, 2h TTL, HttpOnly)
zclaw_refresh_token (path:/api/v1/auth, 7d TTL, HttpOnly)
Secure: dev=false, prod=true | SameSite=Strict
前端存储:
→ Tauri: OS keyring → saasStore.token
→ 浏览器: HttpOnly Cookie (JS 不可读)
→ localStorage: 仅 saasUrl + account 非敏感信息
```
### 集成契约
| 方向 | 接口 | 说明 |
|------|------|------|
| Provides --> saas | auth_middleware, JWT validation, rate limiting | 每个 API 请求经过认证层 |
| Provides --> desktop | secure_storage, crypto_utils | 配置/凭据安全存储 |
| Provides --> admin | admin_guard_middleware | Admin 路由权限验证 |
### Auth API 接口
**公开路由:**
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/auth/register` | 注册 (邮箱 RFC 5322 + 254 字符) |
| POST | `/api/v1/auth/login` | 登录 (5 次/分钟 IP 限流) |
| POST | `/api/v1/auth/refresh` | Token 刷新 (单次使用, 旧 token 撤销) |
| POST | `/api/v1/auth/logout` | 登出 |
**受保护路由:**
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/v1/auth/me` | 当前用户信息 |
| PUT | `/api/v1/auth/password` | 修改密码 (触发 pwv 失效) |
| POST | `/api/v1/auth/totp/setup` | TOTP 设置 (生成 QR) |
| POST | `/api/v1/auth/totp/verify` | TOTP 验证激活 |
| POST | `/api/v1/auth/totp/disable` | TOTP 禁用 (需密码) |
**Tauri 安全命令:**
| 命令 | 说明 |
|------|------|
| `secure_store_set` | OS Keyring 存储 |
| `secure_store_get` | OS Keyring 读取 |
| `secure_store_delete` | OS Keyring 删除 |
| `secure_store_is_available` | Keyring 可用性检测 |
## 代码逻辑
### JWT Password Version 失效机制
```
JWT Claims 含 pwv (password_version) 字段
→ auth_middleware 每次验证 JWT 时: Claims.pwv vs DB.pwv
→ 不匹配 → 401 Unauthorized
→ 修改密码 → DB.pwv 递增 → 所有旧 JWT 自动失效
→ 无需 token 黑名单,验证成本 O(1)
```
### 密码存储: Argon2id + OsRng
```
注册/修改密码:
→ OsRng 生成随机盐
→ Argon2id 哈希 (内存硬 + 时间成本)
→ 存储到 users.password_hash
验证:
→ Argon2id::verify(password, stored_hash)
→ 失败计数递增 → 5 次后锁定 15 分钟
```
### TOTP / API Key 加密: AES-256-GCM
```
TOTP 密钥存储:
→ 随机生成 12 字节 Nonce
→ AES-256-GCM 加密 (密钥: ZCLAW_TOTP_ENCRYPTION_KEY, 64 hex)
→ 存储 nonce + ciphertext
解密:
→ 取出 nonce → AES-256-GCM 解密
→ 解密失败: warn + 跳过 (不阻塞认证)
Provider API Key 同理: heal_provider_keys() 启动时重新加密有效 Key
```
### Token 刷新轮换
```
POST /api/v1/auth/refresh
→ 验证 refresh_token 有效性
→ 检查旧 token 是否已撤销 (rotation 防重放)
→ 撤销旧 refresh_token (写入 DB revoked_at)
→ 签发新 access_token (2h) + refresh_token (7d)
```
### 限流规则
| 端点 | 限制 | 持久化 |
|------|------|--------|
| `/api/auth/login` | 5 次/分钟/IP | PostgreSQL |
| `/api/auth/register` | 3 次/小时/IP | PostgreSQL |
| 公共端点 | 20 次/分钟/IP | 内存 |
### SaaS HTTP 中间件栈 (10 层)
| # | 中间件 | 路由组 | 功能 |
|---|--------|--------|------|
| 1 | public_rate_limit | Public | IP 限流 |
| 2 | auth_middleware | Protected+Relay | JWT/Cookie/API Token 身份验证 |
| 3 | rate_limit_middleware | Protected+Relay | 账户级频率限制 |
| 4 | quota_check_middleware | Relay | 月度配额检查 |
| 5 | request_id_middleware | All | UUID 请求追踪 |
| 6 | api_version_middleware | All | API 版本头 |
| 7 | TimeoutLayer (15s) | Protected | 非流式请求超时 |
| 8 | admin_guard | Admin 子路由 | admin 权限验证 |
| G1 | TraceLayer | All | HTTP 请求追踪 |
| G2 | CorsLayer | All | CORS 白名单 |
## 活跃问题 + 陷阱
| 问题 | 级别 | 说明 |
|------|------|------|
| CSP 已加固 | Done | Tauri 移除 `unsafe-inline` script`connect-src` 限制 `http://localhost:*` + `https://*` |
| TLS 依赖反向代理 | 长期 | Axum 不负责 TLSnginx/caddy 提供 HTTPS 终止 |
| Cookie Secure 开发环境 false | 设计意图 | 开发环境 HTTP 无 Secure生产必须 true |
陷阱:
- JWT 签名密钥: `#[cfg(debug_assertions)]` 有 fallbackrelease 模式直接 `bail` 拒绝启动
- TOTP 加密密钥: 生产必须独立设置 `ZCLAW_TOTP_ENCRYPTION_KEY`,不从 JWT 密钥派生
- CORS 白名单: 生产缺失拒绝启动,不允许通配符
- Refresh Token: 单次使用logout 时撤销到 DBrotation 校验已撤销的旧 token
## 变更日志
| 日期 | 变更 | 提交 |
|------|------|------|
| 2026-04-21 | 移除数据脱敏中间件 (稳定化约束) | fa5ab4e |
| 2026-04-17 | E2E 测试安全链路验证通过 | — |
| 2026-04-16 | Agent 隔离修复 + Admin 权限校验 | — |
| 2026-04-13 | 安全渗透测试 V1: 15 项修复 | — |
| 2026-04-09 | CSP 加固 + JWT pwv + 账户锁定 + TOTP 解耦 | — |
### 测试覆盖
| 功能 | 测试文件 |
|------|---------|
| 认证流程 | `crates/zclaw-saas/tests/auth_test.rs` |
| 认证安全边界 | `crates/zclaw-saas/tests/auth_security_test.rs` |
| 账户安全 | `crates/zclaw-saas/tests/account_security_test.rs` |
| 权限矩阵 | `crates/zclaw-saas/tests/permission_matrix_test.rs` |
| TOTP | `crates/zclaw-saas/src/auth/totp.rs` inline tests |
| 本地加密 | `desktop/src-tauri/src/memory/crypto.rs` inline tests |