# 需求文档 — 预签名URL方案修复文件路径未授权访问漏洞

> Spec: oss-presigned-url
> 版本: v2.0
> 更新时间: 2026-06-04
> 变更说明：升级为多租户跨服务架构，@OssUrl 注解在所有微服务中可用

## 需求原文

针对安全扫描发现的"图片下载或浏览路径未授权访问"中危漏洞，采用 Jackson 序列化器 + 预签名 URL 方案进行修复。在实体字段上标注 `@OssUrl` 注解，API 序列化时自动将对象路径转为带过期时间的预签名 URL，使文件访问凭证临时化。同时需在 `OssTemplate` 接口层统一新增预签名方法，支持所有存储后端（MinIO、阿里云、腾讯云、华为云、S3、七牛、本地存储）。

**v2.0 扩展**：`@OssUrl` 注解定义在 `blade-starter-oss`（核心模块），所有微服务均可使用。通过 Cache+Feign 获取各租户独立的 OSS 配置，本地创建 OssTemplate 生成预签名 URL（纯 CPU 计算，微秒级）。

## 需求概述

| 维度 | 说明 |
|------|------|
| 问题 | MinIO bucket 允许匿名读取，文件直链可被未授权访问 |
| 方案 | 将 bucket 设为私有 + Jackson 序列化器自动生成预签名 URL |
| 范围 | 框架层（blade-starter-oss）+ API 层（blade-resource-api）+ 服务层（blade-resource） |
| 兼容性 | 兼容数据库中已有的完整 URL 和对象路径两种格式，无需数据迁移 |
| 多租户 | 各租户独立的 OSS 配置通过 Feign+Cache 获取，本地生成签名，无网络开销 |

## 用户故事

### R-001: OssTemplate 统一预签名接口

**角色**: 开发者
**功能**: 在 `OssTemplate` 接口中新增 `presignedUrl` 方法，各存储模板（MinIO、Ali、Tencent、Huawei、S3、Qiniu、LocalFile）分别实现
**价值**: 序列化器面向接口编程，不关心底层存储类型，利用多态自动适配当前租户的存储后端

### R-002: `@OssUrl` 注解 + Jackson Module 序列化器（全服务可用）

**角色**: 开发者
**功能**: 在 `blade-starter-oss` 核心模块定义 `@OssUrl` 注解和 `OssUrlSerializer` 序列化器（通过 `OssUrlJacksonModule` 注册到 write ObjectMapper），所有微服务均可使用
**价值**: 声明式使用，字段标注注解即可在任意服务生效，无需在业务代码中手动处理 URL 转换

### R-003: 过期时间两级配置

**角色**: 运维 / 开发者
**功能**: 全局配置（`oss.presigned-expires`，默认 1800 秒）+ 注解级覆盖（`@OssUrl(expires=N)`）
**价值**: 全局统一管控过期时间，特殊字段可单独覆盖，灵活且集中

### R-004: 旧数据兼容

**角色**: 开发者
**功能**: 序列化器自动识别数据库中的完整 URL 和对象路径两种格式，提取对象路径后生成预签名 URL
**价值**: 无需数据迁移，新旧数据并存透明兼容，修复上线零风险

### R-005: 降级保护

**角色**: 系统
**功能**: 序列化器内置降级（空值 → Holder 未初始化 → 签名异常），任何情况不影响 API 响应
**价值**: 即使签名生成异常或配置缺失，也不会导致整个接口报错

### R-006: 多租户独立 OSS 配置（v2.0 新增）

**角色**: 系统
**功能**: 通过 Feign 调用 blade-resource 获取各租户的 OSS 配置，Cache-Aside 缓存后本地创建 OssTemplate，参照 OssDataRule 逻辑判断多租户路径模式
**价值**: 各租户可使用独立的 OSS 配置（endpoint/accessKey/secretKey），消费服务无需感知差异

### R-007: @ConditionalOnMissingBean 桥接（v2.0 新增）

**角色**: 系统
**功能**: 核心模块提供 `DefaultOssPresignHandler`（`@ConditionalOnMissingBean`），API 模块通过 `@AutoConfigureBefore` 注册 `MultiTenantOssPresignHandler` 覆盖默认实现
**价值**: 引入 `blade-resource-api` 的服务自动获得多租户支持，未引入的使用默认单模板实现

### R-008: 兼容 @Sensitive 注解（v2.0 新增）

**角色**: 开发者
**功能**: `OssUrlSerializer` 实现 `ContextualSerializer`，检测到 `@Sensitive` 注解时委托给 `SensitiveSerializer` 处理
**价值**: 字段可同时标注 `@OssUrl` 和 `@Sensitive`，脱敏优先，不冲突

## 验收标准

### AC-001: 预签名接口多态调用
- GIVEN 系统配置了 MinIO 存储后端
- WHEN `OssTemplate.presignedUrl("upload/test.png", 1800)` 被调用
- THEN 返回的 URL 包含签名参数（如 `X-Amz-Signature`）且在 1800 秒内有效

### AC-002: 注解驱动的序列化转换（全服务）
- GIVEN 实体字段标注了 `@OssUrl`
- WHEN 该实体被 Jackson 序列化为 JSON（在任意微服务中）
- THEN 字段值从对象路径变为预签名 URL

### AC-003: 过期时间配置优先级
- GIVEN 全局配置 `oss.presigned-expires=1800`，字段注解 `@OssUrl(expires=3600)`
- WHEN 序列化该字段
- THEN 预签名 URL 的过期时间为 3600 秒（注解覆盖全局）

### AC-004: 过期时间配置默认值
- GIVEN 全局配置 `oss.presigned-expires=1800`，字段注解 `@OssUrl`（未指定 expires）
- WHEN 序列化该字段
- THEN 预签名 URL 的过期时间为 1800 秒（使用全局配置）

### AC-005: 旧数据兼容 — 完整 URL
- GIVEN 数据库字段值为 `http://10.77.65.2:9800/ieslab/upload/20250722/xxx.png`
- WHEN 序列化该字段
- THEN 自动提取对象路径 `upload/20250722/xxx.png` 并生成预签名 URL

### AC-006: 旧数据兼容 — 对象路径
- GIVEN 数据库字段值为 `upload/20250722/xxx.png`
- WHEN 序列化该字段
- THEN 直接使用该路径生成预签名 URL

### AC-007: 降级 — 签名异常
- GIVEN MinIO 服务不可用
- WHEN 预签名 URL 生成抛出异常
- THEN 原样返回字段值，API 响应正常

### AC-008: 各存储后端均支持
- GIVEN 系统分别配置了 MinIO / Ali / Tencent / Huawei / S3 / Qiniu / LocalFile
- WHEN `OssTemplate.presignedUrl()` 被调用
- THEN 各存储后端均能返回有效的临时访问 URL

### AC-009: 多租户独立配置（v2.0 新增）
- GIVEN 租户 A 使用 MinIO，租户 B 使用阿里云 OSS
- WHEN 分别序列化各租户的 `@OssUrl` 字段
- THEN 各自使用对应存储后端生成预签名 URL

### AC-010: Cache-Aside 缓存生效（v2.0 新增）
- GIVEN 首次请求某租户的 `@OssUrl` 字段
- WHEN Feign 调用成功并缓存 OSS 配置
- THEN 后续请求命中缓存，无 Feign 网络调用，预签名 URL 为纯 CPU 计算

### AC-011: @Sensitive 兼容（v2.0 新增）
- GIVEN 字段同时标注了 `@OssUrl` 和 `@Sensitive`
- WHEN 序列化该字段
- THEN 优先执行脱敏处理，不生成预签名 URL

## 范围外事项

- MinIO bucket 策略变更（由运维在部署时执行，不属于代码开发范围）
- 前端缓存机制调整（如有）
- 数据库历史数据迁移（设计上已兼容，无需迁移）

## 约束与假设

1. 预签名 URL 生成为纯 CPU 计算（HMAC + 字符串拼接），不涉及网络 IO（缓存命中后），性能可接受
2. `blade-starter-oss` 模块有 Jackson 依赖（通过 blade-core-tool 传递）
3. 序列化器通过 `OssUrlJacksonModule` 注册到 write ObjectMapper，兼容 `@Sensitive` 注解（`createContextual` 委托）
4. 本地存储（LocalFile）无签名机制，返回后端代理 URL，经网关认证访问
5. `OssPresignConfig` DTO 不含 `@Sensitive` 注解，确保 Feign 传输原始凭证值
6. `OssTemplateFactory` 仅在 `blade-resource-api` 中，SDK 依赖以 `provided` scope 引入
