Minio存储分桶方案设计文档 (V2)
1. 概述
为解决原有单一公有存储桶配置下,业务敏感数据(如用户个人信息、业务报告、内部文件等)存在的潜在泄露风险,同时兼顾非敏感静态资源(如Logo、图标、公开文档)的便捷访问需求,特设计此Minio存储分桶方案。本方案通过引入私有桶与公有桶的分离配置,使业务能根据数据敏感性灵活选择存储策略,并通过严格的向后兼容机制,确保存量业务平滑过渡。
2. 设计目标
- 安全隔离:为敏感数据提供私有桶存储,防止未经授权的直接访问。
- 灵活访问:为非敏感静态资源保留公有桶,支持前端直接引用,无需后端代理。
- 业务无感升级(核心):通过版本开关控制,确保所有现有业务(V1)无需任何修改即可正常运行。
- 配置清晰:新增配置项意图明确,与原有配置易于区分。
- 使用便捷:为后端业务提供清晰的API接口,用于获取对应类型的桶;为前端上传提供明确的参数标识。
3. 配置说明
3.1 原有配置 (V1兼容模式)
此配置保持完全不变,用于兼容所有现有业务。当 minio.bucket.edition 未配置或不为 v2 时,系统运行于此模式。
# Minio服务地址
minio.endpoint=http://192.168.175.54:9000
# 访问密钥
minio.accessKey=snest
# 私密密钥
minio.secretKey=12345678
# 默认存储桶名称(在此模式下,此桶被视为公有桶)
minio.bucketName=apps
3.2 新增V2版本配置
当开启V2版本后,系统启用公有、私有分桶模式。需在原有配置基础上增加如下两项:
# ========== 原有配置 ==========
minio.endpoint=http://192.168.175.54:9000
minio.accessKey=snest
minio.secretKey=12345678
minio.bucketName=apps
# ========== 新增V2配置 ==========
# 功能版本开关,必须显式设置为 `v2` 才能启用新特性
minio.bucket.edition=v2
# 公有桶名称,用于存放可公开访问的资源(如Logo,图标)
minio.iidpBucket=iidp-bucket
配置解读:
-
minio.bucket.edition=v2:此配置为总开关。仅当应用读取到此配置且值为v2时,才会激活下文所述的所有V2新特性(包括分桶逻辑和新接口)。 -
minio.iidpBucket:此配置定义了一个公有桶的名称。 - 原有的
minio.bucketName所指向的桶(apps),在V2模式下,其语义变更为私有桶。
4. 接口变更与使用说明
4.1 后端接口
引擎层将提供以下接口供业务代码调用:
| 接口方法 | 版本 | 返回桶类型 | 说明 |
|---|---|---|---|
getBucket() |
V1 & V2 | V1:公有桶 V2:私有桶 |
兼容性核心接口。V1模式下,返回原minio.bucketName(公有桶);V2模式下,返回原minio.bucketName,但其性质为私有桶。现有业务代码无需任何修改。
|
getIIDPBucket() |
仅 V2 | 公有桶 |
新增接口。仅在配置了minio.bucket.edition=v2时有效,返回由minio.iidpBucket配置指定的公有桶对象。 |
后端业务代码调整示例:
// 场景1:访问敏感业务数据(如用户上传的身份证照),应使用私有桶
MinioClient privateBucketClient = minioService.getBucket();
privateBucketClient.putObject(...); // 此文件默认无法通过URL直接访问
// 场景2:访问或存储公开资源(如网站logo),应使用公有桶
// 注意:此调用在V1配置下会报错或返回null,需确保在V2模式下使用
if (isV2Mode()) { // 业务可根据需要判断版本
MinioClient publicBucketClient = minioService.getIIDPBucket();
publicBucketClient.putObject(...); // 此文件生成URL可直接被前端访问
}
4.2 前端上传联动
前端文件上传组件需根据业务场景,在调用后端通用上传接口时,增加 bucketType 参数。
-
上传到公有桶:
bucketType = 'public' -
上传到私有桶:
bucketType = 'private'或 不传/传其他值(由后端定义默认逻辑,建议默认私有桶以保证安全)。
前端示例(伪代码):
// 上传公开Logo
uploadFile(file, {bucketType: 'public'}).then(url => {
// 获取的url可直接用于<img>标签src
});
// 上传用户私有文档
uploadFile(file, {bucketType: 'private'}).then(url => {
// 此url通常无法直接访问,需通过后端有权限校验的接口代理访问或生成临时签名URL
});
5. 使用场景总结
| 场景 | 存储桶类型 | 配置版本 | 后端使用接口 | 前端上传参数 | 特点 |
|---|---|---|---|---|---|
| 历史存量业务 | 公有桶 | V1 (默认) | getBucket() |
无要求,按原逻辑 | 完全兼容,零成本迁移。 |
|
新业务-敏感数据 (用户数据、交易记录) |
私有桶 | V2 | getBucket() |
bucketType: ‘private’ |
数据安全,外网不可直接访问。 |
|
新业务-公开资源 (Logo、帮助文档、宣传图) |
公有桶 | V2 | getIIDPBucket() |
bucketType: ‘public’ |
访问高效,可直接通过URL访问。 |
6. 实施步骤建议
- 评估与规划:梳理现有业务数据,明确哪些应迁至私有桶,哪些可留在公有桶。
-
配置升级:在应用配置中,将
minio.bucket.edition设置为v2,并设置minio.iidpBucket名称。 -
Minio服务端准备:确保Minio服务中已创建
iidp-bucket桶,并检查apps桶的访问策略是否符合私有要求。 -
后端代码更新:对于新业务逻辑或需要存储公开资源的存量业务,将对应代码中的存储客户端获取方式改为
getIIDPBucket()。 -
前端代码更新:改造文件上传组件,支持传递
bucketType参数。 - 测试与验证:全面测试V2模式下公有、私有桶的上传、访问(直接访问和签名URL访问)功能,并验证V1兼容模式仍正常工作。
文档版本:V1.0
–
业务和公共分桶方案
现状分析
关于 minio 对象存储现在是统一在 application-dev.properties 中配置:
minio.endpoint=http://192.168.175.54:9000
minio.accessKey=snest
minio.secretKey=12345678
minio.bucketName=apps
minio.publicBucket=public-bucket # 没用
# minio.region = oss-cn-hangzhou
调用 meta_attachment 元模型的 upload 方法上传文件,所有的业务都统一使用同一个桶apps.
所有的业务都共享同一个桶,导致同一个桶中存储的文件,无法区分是哪个业务上传的。
需求分析
目前了解到的需求是: 为了安全考虑不让公网直接访问minio,我们让他们开放,如果他们开放了就会所有人都能直接访问到这个桶,那业务附件也被访问到了,这明显存在安全问题。 所以要把系统可以公开的公共资源放入到公共桶,可以外部访问,私有桶只能程序通过id、secret访问。
MinIO 的权限主要在 存储桶(Bucket)级别 和 对象(Object)级别 进行设置。
-
桶策略 (Bucket Policy):这是最主要的方式。你可以为整个桶或桶内特定目录前缀(Prefix)设置详细的访问规则(Allow/Deny),规定谁(Principal)能对哪些资源(Resource)执行什么操作(Action,如
s3:GetObject)。 - 访问控制列表 (ACL):一种相对传统的权限控制方式,可以为匿名用户或特定用户设置路径级权限。
- 预签名 URL (Presigned URL):这是访问私有资源最安全的方式。后端应用使用自己的 Access Key 和 Secret Key 生成一个有时效性的临时网址,分享给前端。在有效期内,即使是没有凭证的用户也能通过此链接访问或上传特定对象,过期后自动失效。
以下是三种常见的桶权限级别对比:
| 权限类型 | 描述 | 适用场景 | 风险等级 |
|---|---|---|---|
private |
完全私有,需签名 URL | 敏感数据、业务附件 | 低 |
download |
只读公开,允许列出和下载文件 | 公开文档、静态资源 | 中 |
public |
完全公开,允许读写删 | 公共上传区(谨慎使用) | 高 |
对应的方案是:创建一个 public (或 download) 桶存放公共资源,一个 private 桶存放业务附件。
目前实施方案
目前实施的方案是为平台新增一个公有桶,平台业务涉及到的对象需存储到公有桶中,其他非业务依然沿用原有桶。 但是需要引擎和平台app做相应的调整。
-
- 新增两个配置项,分别是后端配置和前端配置;
# 后端配置项 minio.bucket.edition=v2 minio.iidpBucket=iidp-bucket # 前端配置项 bucketType = public/private
- 新增两个配置项,分别是后端配置和前端配置;
-
- 为什么要有两个配置项
-
2.1. 后端配置功能开关,是为了兼容已有的系统,如果没有配置
minio.bucket.edition=v2则所有逻辑都保持原来一致,如果有配置则按照新逻辑执行; -
2.2. 前端配置是为了给业务一个选项,因为暴露给前端的接口可能由平台调用也可能由业务调用,所以需要传递一个标识来表明是否需要存储到共有桶,默认行为与原有逻辑保持一致。
-
- 调整
-
3.1. 引擎在
/file/upload接口需要处理两个配置项,即前端和后端,只有两个配置项都满足的情况下才执行新逻辑,否则依然是原来逻辑。 -
3.2. 类似地,平台app 也需要处理两个配置项,但是如果只有一个配置项那就只需要处理一个,比如不需要前端页面来上传。
-
- 样板代码
// 获取 minioTemplate 实例
MinioTemplate minioTemplate = MinioTemplate.getInstance();
// 获取业务所需的桶,有两个选择,如果是非平台的业务则用 getBucket,如果是平台业务则调用 getIIDPBucket
String minioBucketName = minioTemplate.getBucket();
String minioBucketName = minioTemplate.getIIDPBucket();
// 拿到 instance 和 bucket 去使用minio 接口
String objectName = minioTemplate.putObject(byteArrayInputStream, contentType, originalFilename, bucket);
// 其他接口等
// 同时支持获取预签名的url。 presignedGetObjectUrl 签名是的Get方法的url,类似地有 presignedPostObjectUrl
String url = minioTemplate.presignedGetObjectUrl("apps-bucket", "test.json");
System.out.println(url);
// 也可以自行指定具体的method,比如 HttpMethod.PUT,HttpMethod.GET 等,根据业务场景来获取指定的方法权限
url = minioTemplate.presignedObjectUrl(HttpMethod.PUT, "apps-bucket", "test.json");
System.out.println(url);
⚙️ 二、方案实施详细步骤
步骤 1: 规划与创建存储桶
-
创建两个桶:
-
public-bucket: 用于存放可以公开访问的静态资源,如网站图片、CSS、JS、公共文档等。 -
private-bucket: 用于存放所有业务附件、用户上传的私人文件等敏感数据。
-
-
使用
mc命令行工具创建桶:# 设置 MinIO 服务器别名(以本地为例) mc alias set myminio http://your-minio-server:9000 your-admin-user your-admin-password # 创建两个桶 mc mb myminio/public-bucket mc mb myminio/private-bucket
步骤 2: 配置桶权限策略
-
将公共桶设置为
download(只读) 权限:# 设置整个 public-bucket 为只读(允许匿名下载) mc anonymous set download myminio/public-bucket这样,任何人都可以通过直接的 URL(如
http://your-minio-server:9000/public-bucket/image.jpg)访问这个桶里的对象,但不能上传、修改或删除。 -
确保私有桶权限为
private:# 确保 private-bucket 是私有状态(默认通常是 private) mc anonymous set none myminio/private-bucket此举确保了任何未经认证的请求(包括直接通过浏览器访问对象URL)都会被拒绝,返回
Access Denied。
步骤 3: 实现安全的私有对象访问机制
这是最关键的一步。对于私有桶中的业务附件,绝对不能直接暴露 URL,必须通过后端应用生成预签名 URL。
以下是一个 Spring Boot 集成 MinIO 并生成预签名 URL 的示例:
-
后端服务配置与代码示例:
-
application.yml 配置:
minio.public-bucket: public-bucket minio.private-bucket: private-bucket -
Service 层:生成预签名 URL:
@Service @RequiredArgsConstructor public class MinioService { private final MinioClient minioClient; private final MinioProperties properties; // 读取配置的类 /** * 为私有文件生成一个有时效性的下载链接(例如7天) * @param objectName 对象在私有桶中的名称 * @return 预签名的临时URL */ public String getPresignedDownloadUrl(String objectName) throws Exception { return minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) // 指定GET方法用于下载 .bucket(properties.getPrivateBucket()) .object(objectName) .expiry(7, TimeUnit.DAYS) // 设置链接7天后过期 .build() ); } // ... 其他方法如文件上传等 } -
Controller 层:
@RestController @RequestMapping("/api/files") @RequiredArgsConstructor public class FileController { private final MinioService minioService; @GetMapping("/download-url") public ResponseEntity<String> getDownloadUrl(@RequestParam String filename) { try { // 1. 此处可添加业务逻辑:验证当前用户是否有权限下载此文件 // 2. 如果有权限,则生成预签名URL String url = minioService.getPresignedDownloadUrl(filename); return ResponseEntity.ok(url); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error generating URL"); } } }
-
application.yml 配置:
-
前端访问流程:
- 用户点击“下载附件”按钮。
- 前端调用后端 API (
/api/files/download-url?filename=xxx)。 - 后端校验用户身份和权限后,调用 MinIO 服务生成一个临时的预签名 URL 并返回给前端。
- 前端接收到这个临时 URL 后,自动重定向或使用
window.location.href打开它,即可下载文件。 - 这个临时 URL 过期即失效,即使被他人获取,风险也在可控范围内。
💎 总结
通过 “公共桶只读 + 私有桶完全隔离 + 后端预签名” 的组合方案,可以完美解决需求中的安全问题。
- 对于公共资源,利用 MinIO 的
download策略提供高效便捷的直接访问。 - 对于业务附件等敏感数据,通过
private策略彻底关闭公网直连,所有访问必须经由后端应用授权后,通过有时效的预签名 URL 进行。
这样既满足了业务功能,又极大地提升了系统的安全性。实施时,请务必记得结合网络隔离和 HTTPS 等额外安全措施。
在通过后端生成预签名 URL(Presigned URL)提供 MinIO 资源访问权限之前,必须对请求的用户进行身份校验。这个过程的核心是:业务后端的用户身份体系 与 MinIO 的对象存储权限 是两套独立的系统,需要通过后端应用来“桥接”和“翻译”。
下面详细分析这个身份校验的过程和权限控制的原理。
🔐 一、 身份校验与权限控制流程
整个流程的核心在于,最终用户不直接拥有 MinIO 的权限,而是通过后端应用这个“可信中介”来间接获得临时、可控的资源访问权。下图描绘了预签名 URL 生成和使用的完整流程,其中后端对用户的身份校验是发起一切操作的起点:
这个过程的关键在于:
- 最终用户与后端应用之间,通过你熟悉的业务身份认证体系(如 Cookie/Session 或 JWT)来建立信任。
- 后端应用与 MinIO 之间,通过 MinIO 的 Access Key 和 Secret Key 来建立信任。
- 预签名 URL 则是后端应用用自己的 MinIO 凭证,为特定操作(如 GetObject)生成的一个“临时门票”。最终用户拿着这张“门票”可以直接去找 MinIO“兑换”服务,而无需再经过后端应用。
📝 二、 后端身份校验的具体内容
当后端应用收到用户请求后,它会进行一系列校验,这部分属于业务逻辑,与 MinIO 无关:
- 身份认证 (Authentication): 确认用户是谁。通常通过验证用户提交的 Token(如 JWT) 或 Session Cookie 来实现。
- 权限授权 (Authorization): 判断该用户是否有权执行其请求的操作。这通常需要通过查询数据库或缓存来确认用户的角色、权限等级或是否拥有该文件的访问权。
-
业务规则校验: 根据业务需求进行更细致的控制。例如:
- 该文件是否属于该用户?
- 用户是否处于试用期,禁止下载原始大小图片?
- 用户的下载次数是否已用完?
只有通过了所有这些校验,后端应用才会动用其高级权限,向 MinIO 请求生成预签名 URL。
🔗 三、 用户身份与 MinIO 权限的关联方式
后端应用如何将“用户”这个概念映射到 MinIO 的权限上,通常有两种模式:
-
代理模式(最常见):
- 设置:后端应用使用一个高权限的 MinIO 客户端(拥有读取整个存储桶的权限)。
- 工作方式:所有用户对文件的访问请求,都通过这个高权限客户端来生成预签名 URL。MinIO 只知道请求来自后端应用,不知道最终用户是谁。
- 权限控制:权限控制完全由你的后端业务逻辑决定。MinIO 只是无条件地信任来自后端应用的请求。
- 优点:实现简单,无需在 MinIO 中维护复杂的用户体系。
- 缺点:权限控制粒度完全依赖于后端应用的逻辑。
-
映射模式(更精细):- 设置:为每个应用用户或在 MinIO 中创建一个对应的 IAM 用户或策略(Policy)。
- 工作方式:后端应用在校验完用户身份后,使用对应该用户的 MinIO 凭证来生成预签名 URL。
- 权限控制:权限控制由 MinIO 和业务后端共同完成。MinIO 能知道请求来自于哪个具体用户,并会根据为该用户设置的策略来限制其访问范围(例如,只能访问某个前缀下的文件)。
- 优点:权限控制粒度更细,安全性更高,可以实现不同用户访问不同文件的需求。
- 缺点:需要在 MinIO 中维护大量用户和策略,系统更复杂。
对于绝大多数应用,代理模式 已经完全足够且是推荐的做法。
🛡️ 四、 安全性与最佳实践
- 预签名 URL 的本质是“临时授权”:它解决了让一个无权限的用户临时获得特定资源访问权的问题,但其安全性建立在后端严格的身份校验和 URL 的短暂有效期上。
- 设置短暂的有效期:这是最重要的安全措施。根据场景,将有效期设置为 5 到 15 分钟,即使 URL 被泄露,风险窗口也很短。
-
最小权限原则:用于生成签名的 MinIO 客户端凭证,其权限应被严格限制。如果后端只需要生成下载链接,那么它的权限应只有
s3:GetObject,绝不能赋予s3:PutObject或s3:DeleteObject等权限。 - 监控与审计:记录所有预签名 URL 的生成和使用日志,以便在出现安全事件时进行追踪。
💎 总结
可以明确的是:必须在生成预签名 URL 前进行身份校验。
这个过程可以概括为:
- 用户向后台管理员(后端应用)申请参观证(请求预签名 URL)。
- 管理员(后端应用)首先核查你的身份和权限(业务逻辑校验)。
- 核查通过后,管理员用自己的高级门禁卡(MinIO 凭证)为你生成一张有时效的参观证(预签名 URL)。
- 你拿着这张参观证可以直接进入展厅(MinIO)访问特定展品(对象),门口的保安(MinIO 服务)只认参观证,不再核查你的原始身份。
以上是业界常用方案,对于iidp平台可以结合多租户权限来结合考虑。