业务和公共分桶方案

现状分析

关于 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)级别 进行设置。

  1. 桶策略 (Bucket Policy):这是最主要的方式。你可以为整个桶或桶内特定目录前缀(Prefix)设置详细的访问规则(Allow/Deny),规定谁(Principal)能对哪些资源(Resource)执行什么操作(Action,如 s3:GetObject)。
  2. 访问控制列表 (ACL):一种相对传统的权限控制方式,可以为匿名用户或特定用户设置路径级权限。
  3. 预签名 URL (Presigned URL):这是访问私有资源最安全的方式。后端应用使用自己的 Access Key 和 Secret Key 生成一个有时效性的临时网址,分享给前端。在有效期内,即使是没有凭证的用户也能通过此链接访问或上传特定对象,过期后自动失效。

以下是三种常见的桶权限级别对比:

权限类型 描述 适用场景 风险等级
private 完全私有,需签名 URL 敏感数据、业务附件
download 只读公开,允许列出和下载文件 公开文档、静态资源
public 完全公开,允许读写删 公共上传区(谨慎使用

对应的方案是:创建一个 public (或 download) 桶存放公共资源,一个 private 桶存放业务附件。

目前实施方案

目前实施的方案是为平台新增一个公有桶,平台业务涉及到的对象需存储到公有桶中,其他非业务依然沿用原有桶。 但是需要引擎和平台app做相应的调整。

    1. 新增两个配置项,分别是后端配置和前端配置;
        # 后端配置项
        minio.bucket.edition=v2
        minio.iidpBucket=iidp-bucket
        
        
        # 前端配置项
        bucketType = public/private
    1. 为什么要有两个配置项
    • 2.1. 后端配置功能开关,是为了兼容已有的系统,如果没有配置 minio.bucket.edition=v2 则所有逻辑都保持原来一致,如果有配置则按照新逻辑执行;

    • 2.2. 前端配置是为了给业务一个选项,因为暴露给前端的接口可能由平台调用也可能由业务调用,所以需要传递一个标识来表明是否需要存储到共有桶,默认行为与原有逻辑保持一致。

    1. 调整
    • 3.1. 引擎在 /file/upload 接口需要处理两个配置项,即前端和后端,只有两个配置项都满足的情况下才执行新逻辑,否则依然是原来逻辑。

    • 3.2. 类似地,平台app 也需要处理两个配置项,但是如果只有一个配置项那就只需要处理一个,比如不需要前端页面来上传。

    1. 样板代码
  // 获取 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: 规划与创建存储桶

  1. 创建两个桶
    • public-bucket: 用于存放可以公开访问的静态资源,如网站图片、CSS、JS、公共文档等。
    • private-bucket: 用于存放所有业务附件、用户上传的私人文件等敏感数据。
  2. 使用 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: 配置桶权限策略

  1. 将公共桶设置为 download (只读) 权限
    # 设置整个 public-bucket 为只读(允许匿名下载)
    mc anonymous set download myminio/public-bucket
    

    这样,任何人都可以通过直接的 URL(如 http://your-minio-server:9000/public-bucket/image.jpg)访问这个桶里的对象,但不能上传、修改或删除

  2. 确保私有桶权限为 private
    # 确保 private-bucket 是私有状态(默认通常是 private)
    mc anonymous set none myminio/private-bucket
    

    此举确保了任何未经认证的请求(包括直接通过浏览器访问对象URL)都会被拒绝,返回 Access Denied

步骤 3: 实现安全的私有对象访问机制

这是最关键的一步。对于私有桶中的业务附件,绝对不能直接暴露 URL,必须通过后端应用生成预签名 URL

以下是一个 Spring Boot 集成 MinIO 并生成预签名 URL 的示例:

  1. 后端服务配置与代码示例

    • 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");
              }
          }
      }
      
  2. 前端访问流程

    1. 用户点击“下载附件”按钮。
    2. 前端调用后端 API (/api/files/download-url?filename=xxx)。
    3. 后端校验用户身份和权限后,调用 MinIO 服务生成一个临时的预签名 URL 并返回给前端。
    4. 前端接收到这个临时 URL 后,自动重定向或使用 window.location.href 打开它,即可下载文件。
    5. 这个临时 URL 过期即失效,即使被他人获取,风险也在可控范围内。

💎 总结

通过 “公共桶只读 + 私有桶完全隔离 + 后端预签名” 的组合方案,可以完美解决需求中的安全问题。

  • 对于公共资源,利用 MinIO 的 download 策略提供高效便捷的直接访问。
  • 对于业务附件等敏感数据,通过 private 策略彻底关闭公网直连,所有访问必须经由后端应用授权后,通过有时效的预签名 URL 进行。

这样既满足了业务功能,又极大地提升了系统的安全性。实施时,请务必记得结合网络隔离和 HTTPS 等额外安全措施。


在通过后端生成预签名 URL(Presigned URL)提供 MinIO 资源访问权限之前,必须对请求的用户进行身份校验。这个过程的核心是:业务后端的用户身份体系MinIO 的对象存储权限 是两套独立的系统,需要通过后端应用来“桥接”和“翻译”。

下面详细分析这个身份校验的过程和权限控制的原理。

🔐 一、 身份校验与权限控制流程

整个流程的核心在于,最终用户不直接拥有 MinIO 的权限,而是通过后端应用这个“可信中介”来间接获得临时、可控的资源访问权。下图描绘了预签名 URL 生成和使用的完整流程,其中后端对用户的身份校验是发起一切操作的起点:

sequenceDiagram participant U as 最终用户 participant A as 后端应用服务器 participant M as MinIO 服务 U->>A: 1. 请求下载/上传文件(携带Cookie/JWT) Note right of A: 身份校验阶段 A->>A: 2. 验证用户身份与权限 alt 验证失败 A-->>U: 返回错误(401/403) else 验证成功 Note right of A: 预签名URL生成阶段 A->>M: 3. 使用自身凭证请求预签名URL M-->>A: 4. 返回预签名URL A-->>U: 5. 返回预签名URL Note right of U: 直接访问阶段 U->>M: 6. 使用预签名URL直接访问MinIO M->>M: 7. 验证URL签名及有效期 alt URL验证失败 M-->>U: 返回错误(403) else URL验证成功 M-->>U: 8. 返回请求的文件或允许上传 end end

这个过程的关键在于:

  1. 最终用户后端应用之间,通过你熟悉的业务身份认证体系(如 Cookie/Session 或 JWT)来建立信任。
  2. 后端应用MinIO 之间,通过 MinIO 的 Access Key 和 Secret Key 来建立信任。
  3. 预签名 URL 则是后端应用用自己的 MinIO 凭证,为特定操作(如 GetObject)生成的一个“临时门票”。最终用户拿着这张“门票”可以直接去找 MinIO“兑换”服务,而无需再经过后端应用。

📝 二、 后端身份校验的具体内容

当后端应用收到用户请求后,它会进行一系列校验,这部分属于业务逻辑,与 MinIO 无关:

  • 身份认证 (Authentication): 确认用户是谁。通常通过验证用户提交的 Token(如 JWT)Session Cookie 来实现。
  • 权限授权 (Authorization): 判断该用户是否有权执行其请求的操作。这通常需要通过查询数据库或缓存来确认用户的角色、权限等级或是否拥有该文件的访问权。
  • 业务规则校验: 根据业务需求进行更细致的控制。例如:
    • 该文件是否属于该用户?
    • 用户是否处于试用期,禁止下载原始大小图片?
    • 用户的下载次数是否已用完?

只有通过了所有这些校验,后端应用才会动用其高级权限,向 MinIO 请求生成预签名 URL。

🔗 三、 用户身份与 MinIO 权限的关联方式

后端应用如何将“用户”这个概念映射到 MinIO 的权限上,通常有两种模式:

  1. 代理模式(最常见):
    • 设置:后端应用使用一个高权限的 MinIO 客户端(拥有读取整个存储桶的权限)。
    • 工作方式:所有用户对文件的访问请求,都通过这个高权限客户端来生成预签名 URL。MinIO 只知道请求来自后端应用,不知道最终用户是谁
    • 权限控制:权限控制完全由你的后端业务逻辑决定。MinIO 只是无条件地信任来自后端应用的请求。
    • 优点:实现简单,无需在 MinIO 中维护复杂的用户体系。
    • 缺点:权限控制粒度完全依赖于后端应用的逻辑。
  2. 映射模式(更精细):
    • 设置:为每个应用用户或在 MinIO 中创建一个对应的 IAM 用户或策略(Policy)。
    • 工作方式:后端应用在校验完用户身份后,使用对应该用户的 MinIO 凭证来生成预签名 URL。
    • 权限控制:权限控制由 MinIO 和业务后端共同完成。MinIO 能知道请求来自于哪个具体用户,并会根据为该用户设置的策略来限制其访问范围(例如,只能访问某个前缀下的文件)。
    • 优点:权限控制粒度更细,安全性更高,可以实现不同用户访问不同文件的需求。
    • 缺点:需要在 MinIO 中维护大量用户和策略,系统更复杂。

对于绝大多数应用,代理模式 已经完全足够且是推荐的做法。

🛡️ 四、 安全性与最佳实践

  1. 预签名 URL 的本质是“临时授权”:它解决了让一个无权限的用户临时获得特定资源访问权的问题,但其安全性建立在后端严格的身份校验和 URL 的短暂有效期上。
  2. 设置短暂的有效期:这是最重要的安全措施。根据场景,将有效期设置为 5 到 15 分钟,即使 URL 被泄露,风险窗口也很短。
  3. 最小权限原则:用于生成签名的 MinIO 客户端凭证,其权限应被严格限制。如果后端只需要生成下载链接,那么它的权限应只有 s3:GetObject,绝不能赋予 s3:PutObjects3:DeleteObject 等权限。
  4. 监控与审计:记录所有预签名 URL 的生成和使用日志,以便在出现安全事件时进行追踪。

💎 总结

可以明确的是:必须在生成预签名 URL 前进行身份校验

这个过程可以概括为:

  1. 用户向后台管理员(后端应用)申请参观证(请求预签名 URL)。
  2. 管理员(后端应用)首先核查你的身份和权限(业务逻辑校验)。
  3. 核查通过后,管理员用自己的高级门禁卡(MinIO 凭证)为你生成一张有时效的参观证(预签名 URL)。
  4. 你拿着这张参观证可以直接进入展厅(MinIO)访问特定展品(对象),门口的保安(MinIO 服务)只认参观证,不再核查你的原始身份。

以上是业界常用方案,对于iidp平台可以结合多租户权限来结合考虑。