\345\257\271\350\261\241\345\255\230\345\202\250\346\235\203\351\231\220\350\256\276\350\256\241\346\226\271\346\241\210.md
... ...
@@ -0,0 +1,248 @@
1
+### 业务和公共分桶方案
2
+
3
+#### 现状分析
4
+
5
+关于 minio 对象存储现在是统一在 application-dev.properties 中配置:
6
+```properties
7
+minio.endpoint=http://192.168.175.54:9000
8
+minio.accessKey=snest
9
+minio.secretKey=12345678
10
+minio.bucketName=apps
11
+minio.publicBucket=public-bucket # 没用
12
+# minio.region = oss-cn-hangzhou
13
+```
14
+调用 `meta_attachment` 元模型的 `upload` 方法上传文件,所有的业务都统一使用同一个桶apps.
15
+所有的业务都共享同一个桶,导致同一个桶中存储的文件,无法区分是哪个业务上传的。
16
+
17
+
18
+#### 需求分析
19
+目前了解到的需求是:
20
+为了安全考虑不让公网直接访问minio,我们让他们开放,如果他们开放了就会所有人都能直接访问到这个桶,那业务附件也被访问到了,这明显存在安全问题。
21
+所以要把系统可以公开的公共资源放入到公共桶,可以外部访问,私有桶只能程序通过id、secret访问。
22
+
23
+MinIO 的权限主要在 **存储桶(Bucket)级别** 和 **对象(Object)级别** 进行设置。
24
+
25
+1. **桶策略 (Bucket Policy)**:这是最主要的方式。你可以为整个桶或桶内特定目录前缀(Prefix)设置详细的访问规则(Allow/Deny),规定谁(Principal)能对哪些资源(Resource)执行什么操作(Action,如 `s3:GetObject`)。
26
+2. **访问控制列表 (ACL)**:一种相对传统的权限控制方式,可以为匿名用户或特定用户设置路径级权限。
27
+3. **预签名 URL (Presigned URL)**:这是访问私有资源最安全的方式。后端应用使用自己的 Access Key 和 Secret Key 生成一个**有时效性的临时网址**,分享给前端。在有效期内,即使是没有凭证的用户也能通过此链接访问或上传特定对象,过期后自动失效。
28
+
29
+以下是三种常见的桶权限级别对比:
30
+
31
+| 权限类型 | 描述 | 适用场景 | 风险等级 |
32
+|:---------------| :--- | :--- | :--- |
33
+| **`private`** | **完全私有**,需签名 URL | 敏感数据、业务附件 | **低** |
34
+| **`download`** | **只读公开**,允许列出和下载文件 | 公开文档、静态资源 | **中** |
35
+| **`public`** | **完全公开**,允许读写删 | 公共上传区(**谨慎使用**) | **高** |
36
+
37
+对应的方案是:**创建一个 `public` (或 `download`) 桶存放公共资源,一个 `private` 桶存放业务附件。**
38
+
39
+### ⚙️ 二、方案实施详细步骤
40
+
41
+#### 步骤 1: 规划与创建存储桶
42
+
43
+1. **创建两个桶**:
44
+ * `public-bucket`: 用于存放可以公开访问的静态资源,如网站图片、CSS、JS、公共文档等。
45
+ * `private-bucket`: 用于存放所有业务附件、用户上传的私人文件等敏感数据。
46
+
47
+2. **使用 `mc` 命令行工具创建桶**:
48
+ ```bash
49
+ # 设置 MinIO 服务器别名(以本地为例)
50
+ mc alias set myminio http://your-minio-server:9000 your-admin-user your-admin-password
51
+
52
+ # 创建两个桶
53
+ mc mb myminio/public-bucket
54
+ mc mb myminio/private-bucket
55
+ ```
56
+
57
+#### 步骤 2: 配置桶权限策略
58
+
59
+1. **将公共桶设置为 `download` (只读) 权限**:
60
+ ```bash
61
+ # 设置整个 public-bucket 为只读(允许匿名下载)
62
+ mc anonymous set download myminio/public-bucket
63
+ ```
64
+ 这样,任何人都可以通过直接的 URL(如 `http://your-minio-server:9000/public-bucket/image.jpg`)访问这个桶里的对象,但**不能上传、修改或删除**。
65
+
66
+2. **确保私有桶权限为 `private`**:
67
+ ```bash
68
+ # 确保 private-bucket 是私有状态(默认通常是 private)
69
+ mc anonymous set none myminio/private-bucket
70
+ ```
71
+ 此举确保了**任何未经认证的请求(包括直接通过浏览器访问对象URL)都会被拒绝**,返回 `Access Denied`。
72
+
73
+#### 步骤 3: 实现安全的私有对象访问机制
74
+
75
+这是最关键的一步。对于私有桶中的业务附件,**绝对不能直接暴露 URL**,必须通过后端应用生成**预签名 URL**。
76
+
77
+以下是一个 Spring Boot 集成 MinIO 并生成预签名 URL 的示例:
78
+
79
+1. **后端服务配置与代码示例**:
80
+
81
+ * **application.yml 配置**:
82
+ ```yaml
83
+ minio.public-bucket: public-bucket
84
+ minio.private-bucket: private-bucket
85
+ ```
86
+
87
+ * **Service 层:生成预签名 URL**:
88
+ ```java
89
+ @Service
90
+ @RequiredArgsConstructor
91
+ public class MinioService {
92
+ private final MinioClient minioClient;
93
+ private final MinioProperties properties; // 读取配置的类
94
+
95
+ /**
96
+ * 为私有文件生成一个有时效性的下载链接(例如7天)
97
+ * @param objectName 对象在私有桶中的名称
98
+ * @return 预签名的临时URL
99
+ */
100
+ public String getPresignedDownloadUrl(String objectName) throws Exception {
101
+ return minioClient.getPresignedObjectUrl(
102
+ GetPresignedObjectUrlArgs.builder()
103
+ .method(Method.GET) // 指定GET方法用于下载
104
+ .bucket(properties.getPrivateBucket())
105
+ .object(objectName)
106
+ .expiry(7, TimeUnit.DAYS) // 设置链接7天后过期
107
+ .build()
108
+ );
109
+ }
110
+
111
+ // ... 其他方法如文件上传等
112
+ }
113
+
114
+ ```
115
+
116
+ * **Controller 层**:
117
+ ```java
118
+ @RestController
119
+ @RequestMapping("/api/files")
120
+ @RequiredArgsConstructor
121
+ public class FileController {
122
+ private final MinioService minioService;
123
+
124
+ @GetMapping("/download-url")
125
+ public ResponseEntity<String> getDownloadUrl(@RequestParam String filename) {
126
+ try {
127
+ // 1. 此处可添加业务逻辑:验证当前用户是否有权限下载此文件
128
+ // 2. 如果有权限,则生成预签名URL
129
+ String url = minioService.getPresignedDownloadUrl(filename);
130
+ return ResponseEntity.ok(url);
131
+ } catch (Exception e) {
132
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error generating URL");
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+3. **前端访问流程**:
139
+ 1. 用户点击“下载附件”按钮。
140
+ 2. 前端调用后端 API (`/api/files/download-url?filename=xxx`)。
141
+ 3. 后端校验用户身份和权限后,调用 MinIO 服务生成一个临时的预签名 URL 并返回给前端。
142
+ 4. 前端接收到这个临时 URL 后,自动重定向或使用 `window.location.href` 打开它,即可下载文件。
143
+ 5. **这个临时 URL 过期即失效**,即使被他人获取,风险也在可控范围内。
144
+
145
+### 💎 总结
146
+
147
+通过 **“公共桶只读 + 私有桶完全隔离 + 后端预签名”** 的组合方案,可以完美解决需求中的安全问题。
148
+
149
+* 对于**公共资源**,利用 MinIO 的 `download` 策略提供高效便捷的直接访问。
150
+* 对于**业务附件**等敏感数据,通过 **`private` 策略**彻底关闭公网直连,所有访问必须经由**后端应用授权**后,通过**有时效的预签名 URL** 进行。
151
+
152
+这样既满足了业务功能,又极大地提升了系统的安全性。实施时,请务必记得结合网络隔离和 HTTPS 等额外安全措施。
153
+
154
+
155
+---
156
+
157
+在通过后端生成预签名 URL(Presigned URL)提供 MinIO 资源访问权限之前,**必须对请求的用户进行身份校验**。这个过程的核心是:**业务后端的用户身份体系** 与 **MinIO 的对象存储权限** 是两套独立的系统,需要通过后端应用来“桥接”和“翻译”。
158
+
159
+
160
+下面详细分析这个身份校验的过程和权限控制的原理。
161
+
162
+### 🔐 一、 身份校验与权限控制流程
163
+
164
+整个流程的核心在于,最终用户不直接拥有 MinIO 的权限,而是通过后端应用这个“可信中介”来间接获得临时、可控的资源访问权。下图描绘了预签名 URL 生成和使用的完整流程,其中后端对用户的身份校验是发起一切操作的起点:
165
+
166
+```mermaid
167
+sequenceDiagram
168
+ participant U as 最终用户
169
+ participant A as 后端应用服务器
170
+ participant M as MinIO 服务
171
+ U->>A: 1. 请求下载/上传文件(携带Cookie/JWT)
172
+ Note right of A: 身份校验阶段
173
+ A->>A: 2. 验证用户身份与权限
174
+ alt 验证失败
175
+ A-->>U: 返回错误(401/403)
176
+ else 验证成功
177
+ Note right of A: 预签名URL生成阶段
178
+ A->>M: 3. 使用自身凭证请求预签名URL
179
+ M-->>A: 4. 返回预签名URL
180
+ A-->>U: 5. 返回预签名URL
181
+ Note right of U: 直接访问阶段
182
+ U->>M: 6. 使用预签名URL直接访问MinIO
183
+ M->>M: 7. 验证URL签名及有效期
184
+ alt URL验证失败
185
+ M-->>U: 返回错误(403)
186
+ else URL验证成功
187
+ M-->>U: 8. 返回请求的文件或允许上传
188
+ end
189
+ end
190
+```
191
+
192
+这个过程的关键在于:
193
+
194
+1. **最终用户**与**后端应用**之间,通过你熟悉的业务身份认证体系(如 Cookie/Session 或 JWT)来建立信任。
195
+2. **后端应用**与 **MinIO** 之间,通过 MinIO 的 Access Key 和 Secret Key 来建立信任。
196
+3. 预签名 URL 则是后端应用用自己的 MinIO 凭证,为特定操作(如 GetObject)生成的一个“临时门票”。最终用户拿着这张“门票”可以直接去找 MinIO“兑换”服务,而无需再经过后端应用。
197
+
198
+### 📝 二、 后端身份校验的具体内容
199
+
200
+当后端应用收到用户请求后,它会进行一系列校验,这部分属于业务逻辑,与 MinIO 无关:
201
+
202
+* **身份认证 (Authentication)**: 确认用户是谁。通常通过验证用户提交的 **Token(如 JWT)** 或 **Session Cookie** 来实现。
203
+* **权限授权 (Authorization)**: 判断该用户是否有权执行其请求的操作。这通常需要通过查询数据库或缓存来确认用户的角色、权限等级或是否拥有该文件的访问权。
204
+* **业务规则校验**: 根据业务需求进行更细致的控制。例如:
205
+ * 该文件是否属于该用户?
206
+ * 用户是否处于试用期,禁止下载原始大小图片?
207
+ * 用户的下载次数是否已用完?
208
+
209
+**只有通过了所有这些校验**,后端应用才会动用其高级权限,向 MinIO 请求生成预签名 URL。
210
+
211
+### 🔗 三、 用户身份与 MinIO 权限的关联方式
212
+
213
+后端应用如何将“用户”这个概念映射到 MinIO 的权限上,通常有两种模式:
214
+
215
+1. **代理模式(最常见)**:
216
+ * **设置**:后端应用使用一个高权限的 MinIO 客户端(拥有读取整个存储桶的权限)。
217
+ * **工作方式**:所有用户对文件的访问请求,都通过这个高权限客户端来生成预签名 URL。**MinIO 只知道请求来自后端应用,不知道最终用户是谁**。
218
+ * **权限控制**:权限控制完全由你的后端业务逻辑决定。MinIO 只是无条件地信任来自后端应用的请求。
219
+ * **优点**:实现简单,无需在 MinIO 中维护复杂的用户体系。
220
+ * **缺点**:权限控制粒度完全依赖于后端应用的逻辑。
221
+
222
+2. **~~映射模式(更精细)~~**:
223
+ * **设置**:为每个应用用户或在 MinIO 中创建一个对应的 IAM 用户或策略(Policy)。
224
+ * **工作方式**:后端应用在校验完用户身份后,使用**对应该用户的 MinIO 凭证**来生成预签名 URL。
225
+ * **权限控制**:权限控制由 MinIO 和业务后端共同完成。MinIO 能知道请求来自于哪个具体用户,并会根据为该用户设置的策略来限制其访问范围(例如,只能访问某个前缀下的文件)。
226
+ * **优点**:权限控制粒度更细,安全性更高,可以实现不同用户访问不同文件的需求。
227
+ * **缺点**:需要在 MinIO 中维护大量用户和策略,系统更复杂。
228
+
229
+对于绝大多数应用,**代理模式** 已经完全足够且是推荐的做法。
230
+
231
+### 🛡️ 四、 安全性与最佳实践
232
+
233
+1. **预签名 URL 的本质是“临时授权”**:它解决了让一个无权限的用户临时获得特定资源访问权的问题,但其安全性建立在后端严格的身份校验和 URL 的短暂有效期上。
234
+2. **设置短暂的有效期**:这是最重要的安全措施。根据场景,将有效期设置为 5 到 15 分钟,即使 URL 被泄露,风险窗口也很短。
235
+3. **最小权限原则**:用于生成签名的 MinIO 客户端凭证,其权限应被严格限制。如果后端只需要生成下载链接,那么它的权限应只有 `s3:GetObject`,绝不能赋予 `s3:PutObject` 或 `s3:DeleteObject` 等权限。
236
+4. **监控与审计**:记录所有预签名 URL 的生成和使用日志,以便在出现安全事件时进行追踪。
237
+
238
+### 💎 总结
239
+
240
+可以明确的是:**必须在生成预签名 URL 前进行身份校验**。
241
+
242
+这个过程可以概括为:
243
+1. 用户向后台管理员(后端应用)申请参观证(请求预签名 URL)。
244
+2. 管理员(后端应用)首先核查你的身份和权限(业务逻辑校验)。
245
+3. 核查通过后,管理员用自己的高级门禁卡(MinIO 凭证)为你生成一张有时效的参观证(预签名 URL)。
246
+4. 你拿着这张参观证可以直接进入展厅(MinIO)访问特定展品(对象),门口的保安(MinIO 服务)只认参观证,不再核查你的原始身份。
247
+
248
+通过这种设计,你既实现了安全的身份校验,又享受到了预签名 URL 带来的高性能优势。
... ...
\ No newline at end of file