5e42957bf8903593afffca4983e56c7eea4758ff
\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 |