# 需求文档：报告免登访问 + 操作鉴权拦截

## 文档信息

- 创建时间：2026-07-21
- 需求来源：用户反馈
- 关联模块：service_monitor/report + auth

---

## 1. 背景与目标

### 1.1 背景

当前钉钉/企业微信/邮件通知中的报告链接格式为：

```
http://192.168.5.60:8088/service-monitor/report/20260721_143000_ab12cd
```

用户点击链接后存在两个痛点：

1. **必须登录才能看报告**：用户从钉钉消息点击链接 → 跳转到登录页 → 登录后跳转到首页 `/`（不是报告页）→ 用户需要手动找到报告。体验割裂，尤其是手机端操作不便。
2. **登录后无边界控制**：用户通过报告链接进入系统后，如果点击了其他模块（如目标管理、通知配置等），可以自由访问所有功能，没有权限边界。

### 1.2 目标

1. **报告免登访问**：通知中的报告链接携带一次性 token，点击即可直接查看报告，无需登录
2. **操作鉴权拦截**：通过 token 进入报告页的用户，如果点击了其他模块或功能（导航菜单、管理操作等），自动跳转到登录页要求登录
3. **安全性保障**：token 有时效性、一次性使用、不可猜测

---

## 2. 需求详述

### 2.1 报告免登访问

#### 核心流程

```
钉钉通知 → 点击链接（带 token）→ 服务端验证 token → 直接渲染报告页
```

#### Token 设计

| 属性 | 值 | 说明 |
|------|-----|------|
| 格式 | `rpt_<32位随机hex>` | 前缀 `rpt_` 标识报告 token，与 session token 区分 |
| 生成时机 | 报告生成后、发送通知前 | 在 `runner_service.run_inspection_sync()` 中生成 |
| 存储 | 报告 JSON 文件内 `access_token` 字段 | 与报告数据同生命周期 |
| 有效期 | 7 天（可配置） | 超时后 token 失效，需登录查看 |
| 使用次数 | 不限 | 同一 token 可多次访问（钉钉群内多人点击） |
| 失效条件 | 1. 超过有效期 2. 报告被删除 | 报告删除时 token 自然失效 |

#### 链接格式

**改前**：
```
http://192.168.5.60:8088/service-monitor/report/20260721_143000_ab12cd
```

**改后**：
```
http://192.168.5.60:8088/service-monitor/report/20260721_143000_ab12cd?token=rpt_a1b2c3d4e5f6...
```

#### 服务端处理逻辑

1. 访问 `/service-monitor/report/<id>?token=rpt_xxx`
2. 检查 `token` 参数是否存在
3. 如果存在：
   - 从报告 JSON 读取 `access_token` 和 `access_token_expires_at`
   - 验证 token 匹配 + 未过期
   - 验证通过：渲染报告页，标记 `token_auth=True`（只读访客模式）
   - 验证失败：跳转登录页
4. 如果不存在：
   - 检查 session 登录状态（原有逻辑不变）
   - 未登录：跳转登录页

---

### 2.2 操作鉴权拦截

#### 核心概念：只读访客模式

通过 token 进入报告页的用户处于**只读访客模式**，与正常登录用户的关键区别：

| 行为 | 正常登录用户 | 只读访客（token 访问） |
|------|------------|---------------------|
| 查看报告内容 | ✅ | ✅ |
| 导出报告（MD/JSON） | ✅ | ✅ |
| 点击导航菜单（其他模块） | ✅ 正常访问 | ❌ 跳转登录页 |
| 点击管理操作（修复、删除等） | ✅ | ❌ 跳转登录页 |
| 顶部用户信息 | 显示用户名 | 显示"访客" + 登录按钮 |

#### 前端拦截机制

1. **导航菜单拦截**：
   - 报告页模板接收 `token_auth=True` 标记
   - 侧边栏/顶部导航链接绑定点击事件
   - 如果 `token_auth` 为 true，点击任何非报告页链接 → 弹出提示"查看更多功能请先登录" → 跳转登录页

2. **管理操作拦截**：
   - 修复按钮、删除按钮等管理操作，在 `token_auth` 模式下隐藏或禁用
   - 导出功能保留（导出是只读操作，不涉及系统修改）

3. **登录入口**：
   - 报告页顶部显示"访客模式"标识 + "登录查看更多"按钮
   - 点击后跳转登录页，登录成功后回到报告页（需支持 `next` 参数）

---

### 2.3 登录后回跳（next 参数）

#### 当前问题

登录成功后固定跳转 `/`，无法回到原始访问页面。

#### 改进方案

1. 未登录访问受保护页面时，重定向到 `/login?next=<原始URL>`
2. 登录成功后，检查 `next` 参数，有则跳转到 `next`，无则跳转 `/`
3. `next` 参数仅允许本站相对路径（防止开放重定向攻击）

#### 涉及改动

- `routes/auth.py`：GET `/login` 读取 `next` 参数传给模板；POST `/login` 成功后返回 `next`
- `login.html`：登录成功后 `window.location.href = next || '/'`
- `service_monitor/routes.py`：未登录重定向时携带 `next` 参数
- `decorators.py`：`page_login_required` 重定向时携带 `next` 参数

---

## 3. 技术方案

### 3.1 后端改动

| 文件 | 改动 |
|------|------|
| `service_monitor/services/report_service.py` | `save()` 生成 access_token + 过期时间；新增 `validate_access_token()` |
| `service_monitor/services/runner_service.py` | 通知链接拼接 `?token=rpt_xxx` |
| `service_monitor/routes.py` | `page_report()` 支持 token 验证；其他页面路由检查 token_auth 拦截 |
| `routes/auth.py` | 支持 `next` 参数回跳 |
| `decorators.py` | `page_login_required` 重定向携带 `next` |

### 3.2 前端改动

| 文件 | 改动 |
|------|------|
| `templates/service_monitor/report.html` | token_auth 模式：隐藏管理按钮、导航拦截、显示访客标识 |
| `templates/service_monitor/base.html` | 导航菜单 token_auth 拦截逻辑 |
| `templates/login.html` | 支持 `next` 参数回跳 |

### 3.3 数据结构

报告 JSON 新增字段：
```json
{
  "id": "20260721_143000_ab12cd",
  "access_token": "rpt_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "access_token_expires_at": "2026-07-28T14:30:00+08:00",
  ...原有字段...
}
```

---

## 4. 验收标准

| 标准 | 验证方法 |
|------|---------|
| 钉钉通知链接包含 token 参数 | 检查通知消息中的链接格式 |
| 点击带 token 的链接可直接查看报告 | 未登录状态下点击链接 |
| token 过期后访问跳转登录页 | 修改报告 JSON 中的过期时间为过去时间后访问 |
| 报告被删除后 token 失效 | 删除报告后用原 token 访问 |
| token_auth 模式下点击导航菜单跳转登录页 | 在报告页点击侧边栏其他模块 |
| token_auth 模式下管理操作不可用 | 检查修复/删除按钮是否隐藏 |
| token_auth 模式下导出功能可用 | 点击导出 MD/JSON |
| 登录后回跳到原始页面 | 从报告页跳转登录后回到报告页 |
| next 参数防开放重定向 | 构造 `next=http://evil.com` 验证被拦截 |
| 正常登录用户不受影响 | 登录后访问报告页功能完整 |

---

## 5. 风险评估

| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| token 泄露（钉钉群转发） | 非授权人员可查看报告 | token 7 天过期；报告为只读不涉及修改操作 |
| token 暴力猜测 | 理论上可猜中 token | 32 位 hex = 128 bit 熵，暴力破解不可行 |
| 开放重定向攻击 | next 参数指向恶意站点 | next 仅允许本站相对路径，校验不以 `http` 开头 |
| 旧报告无 token | 已有报告无法免登访问 | 仅新生成的报告有 token，旧报告仍需登录查看 |
| 钉钉 Markdown 链接长度 | URL 变长可能超限 | token 32 字符 + 参数约 40 字符，钉钉限制 2048 字节，远未超限 |

---

## 6. 后续规划

1. **token 管理页面**：管理员可查看/撤销报告 token
2. **邮件通知链接同步**：邮件中的报告链接也携带 token
3. **企业微信通知链接同步**：企业微信通知链接也携带 token
