facc04aa65
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交 (5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。 一、docs 结构整改(整改 #14) 根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入, 随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录 树并存于 docs/,共 791 文件、双分类体系冲突。 修复动作: - b2 同名异主题文件改名迁移保全 9 个 - C 类 39 个孤立文件按主题正确归类 - A/B1 类 222 个重复文件删除(新结构已有内容副本) - 9 个旧独有空目录删除 - 270 处内部引用按 verified 映射改写 - 整改记录 #14 登记于 04-运维文档/部署运维 结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。 残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。 二、compose 双目录对齐(消除踩坑 A) - docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist 改为 src/frontend-*/dist(h5 / agent / admin / terminal) - docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/ - 效果:本地 docker compose up 不再把根目录 stale dist 挂回, 与线上一致,分叉隐患消除(已 docker compose config 校验通过) 防复发铁律: - 重构须提交;仓库修复须 git stash -u 或先 commit - 新结构须 git add 并提交,避免再次 untracked 复活 - H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
8.7 KiB
8.7 KiB
Token多IP异常检测 - 技术设计文档
任务ID: 待分配 模块: 威胁检测 优先级: P1 ATT&CK: T1078 (有效账户), T1552 (非安全凭据)
1. 需求概述
1.1 业务背景
当前系统已废弃密码登录,仅支持企微OAuth2/扫码登录。Token是用户身份的唯一凭证,当Token被泄露后,攻击者可能从不同IP使用同一Token访问系统。本功能旨在检测此类异常行为。
1.2 功能目标
- 记录每个Token使用的IP地址
- 检测同一Token在短时间内被多个IP使用的情况
- 触发告警通知安全管理员
- 可选:自动禁用异常Token
2. 技术方案
2.1 架构设计
┌─────────────────────────────────────────────────────────┐
│ 检测流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 用户API请求 │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ record_token_ip │ ← 每次请求记录IP │
│ │ (埋点) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Redis Set │ ← token_ips:{hash} │
│ │ IP集合(1h TTL) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ 定时任务(每分钟) │
│ ┌─────────────────┐ │
│ │ detect_anomaly │ ← 扫描异常Token │
│ │ (定时任务) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ send_alert │ ← 企微机器人告警 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
2.2 数据结构
Redis Key设计
| Key格式 | 类型 | TTL | 说明 |
|---|---|---|---|
token_ips:{token_hash} |
Set | 3600秒 | 记录Token使用的IP集合 |
token_ips:alerted:{token_hash} |
String | 3600秒 | 已告警标记,避免重复 |
Token存储(现有)
| Key格式 | 类型 | 说明 |
|---|---|---|
user:token:{token} |
JSON | 用户信息,含employee_id |
2.3 接口设计
2.3.1 记录Token使用IP (埋点)
# 在 token_service.py 中增加
async def record_token_ip(token: str, ip: str):
"""
记录Token使用的IP地址
Args:
token: 用户Token
ip: 客户端IP (X-Forwarded-For 或 request.client.host)
"""
import hashlib
token_hash = hashlib.sha256(token.encode()).hexdigest()
redis = await get_redis()
key = f"token_ips:{token_hash}"
# 添加IP到Set (自动去重)
redis.sadd(key, ip)
# 设置1小时过期
redis.expire(key, 3600)
2.3.2 异常检测定时任务
# 在 tasks/token_anomaly_detection.py
async def detect_token_anomaly():
"""
检测Token异常使用
扫描所有 token_ips:* keys
当 IP数量 >= 阈值 时触发告警
"""
# 配置
THRESHOLD = 3 # IP数量阈值
WINDOW_SECONDS = 3600 # 时间窗口
redis = await get_redis()
alerted_key_prefix = "token_ips:alerted:"
# 扫描所有 token_ips:* keys
async for key in redis.scan_iter("token_ips:*"):
# 跳过 alerted keys
if key.startswith(alerted_key_prefix):
continue
token_hash = key.replace("token_ips:", "")
ip_count = await redis.scard(key)
if ip_count >= THRESHOLD:
# 检查是否已告警
alerted_key = f"{alerted_key_prefix}{token_hash}"
if await redis.get(alerted_key):
continue # 已告警,跳过
# 获取用户信息
token = await redis.get(f"user:token:{token_hash}")
if token:
user_data = json.loads(token)
employee_id = user_data.get("employee_id")
# 发送告警
await send_security_alert(
title="Token异常告警",
content=f"员工 {employee_id} 的Token被 {ip_count} 个IP使用\nToken: {token_hash[:8]}..."
)
# 标记已告警
await redis.setex(alerted_key, WINDOW_SECONDS, "1")
2.3.3 获取客户端IP
def get_client_ip(request) -> str:
"""获取客户端真实IP"""
# 优先从 X-Forwarded-For 获取
forwarded = request.headers.get("X-Forwarded-For")
if forwarded:
return forwarded.split(",")[0].strip()
# 降级到 request.client.host
return request.client.host if request.client else ""
3. 配置项
3.1 环境变量
| 变量名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TOKEN_ANOMALY_THRESHOLD |
int | 3 | 触发告警的IP数量阈值 |
TOKEN_ANOMALY_WINDOW |
int | 3600 | 时间窗口(秒) |
TOKEN_ANOMALY_AUTO_DISABLE |
bool | false | 是否自动禁用Token |
CONTENT_AUDIT_WEBHOOK |
string | - | 企微机器人webhook(现有) |
4. 告警内容
4.1 告警模板
{
"msgtype": "markdown",
"markdown": {
"content": "🔴 **Token异常告警**\n\n"
"> 员工ID: {employee_id}\n"
"> 异常Token: {token_hash[:8]}...\n"
"> IP数量: {ip_count}\n"
"> 时间: {timestamp}\n\n"
"> **请及时确认是否为本人操作**"
}
}
5. 集成点
5.1 现有组件复用
| 组件 | 用途 |
|---|---|
| Redis | IP存储 |
| APScheduler | 定时任务 |
| content_audit_webhook | 企微告警 |
| token_service.py | Token管理 |
5.2 侵入点
| 文件 | 修改内容 |
|---|---|
app/services/token_service.py |
增加 record_token_ip() |
app/main.py |
注册定时任务 |
app/tasks/token_anomaly_detection.py |
新建检测任务 |
6. 性能与容错
6.1 性能估算
| 指标 | 估算值 |
|---|---|
| Redis存储 | ~50KB (1000活跃Token) |
| 定时任务耗时 | < 100ms |
| 定时任务间隔 | 60秒 |
6.2 容错设计
- 告警发送失败:记录日志,不阻塞主流程
- Redis连接失败:跳过本次检测,下个周期重试
- Token不存在:跳过,不影响其他检测
7. 测试用例
7.1 单元测试
| 用例ID | 描述 | 预期结果 |
|---|---|---|
| T001 | 单IP使用Token | 不触发告警 |
| T002 | 3个IP使用Token | 触发告警 |
| T003 | 5个IP使用Token | 触发告警(严重) |
| T004 | 同一IP多次使用 | 不触发告警 |
7.2 集成测试
| 用例ID | 描述 | 预期结果 |
|---|---|---|
| I001 | 真实Token请求 | IP被记录 |
| I002 | 定时任务执行 | 异常Token被检测 |
| I003 | 告警发送 | 企微收到消息 |
8. 部署清单
8.1 文件变更
| 操作 | 文件 |
|---|---|
| 新增 | app/tasks/token_anomaly_detection.py |
| 修改 | app/services/token_service.py |
| 修改 | app/main.py |
8.2 配置变更
| 操作 | 变量 |
|---|---|
| 新增(可选) | TOKEN_ANOMALY_THRESHOLD |
| 新增(可选) | TOKEN_ANOMALY_AUTO_DISABLE |
9. 回滚方案
如需回滚:
- 移除定时任务注册 (main.py)
- 删除 record_token_ip() 调用
- Redis keys 会在1小时后自动过期
编制人: 威胁检测工程师 日期: 2026-07-14 审核人: 待定