目录

基于S3协议对外分享文件权限管理方案

本篇文章以MinIO作为文件服务端,其他基于S3协议的文件服务也一样。

通过接口返回文件地址给前端时,可以结合 S3 的预签名 URLSTS(临时凭证) 来实现安全访问。


核心思路

  1. 接口返回的文件地址不直接暴露 MinIO 存储路径
  2. 在返回文件地址前,通过后端生成 预签名 URL动态临时凭证
  3. 前端使用该地址访问文件,确保文件访问受到权限和时间限制。

实现方法 1:基于预签名 URL

1.1 后端生成预签名 URL

使用 MinIO 提供的 SDK,在接口中动态生成带有时间限制的预签名 URL,并返回给前端。

Java 示例代码:

import io.minio.MinioClient;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FileController {

    private final MinioClient minioClient;

    public FileController() {
        this.minioClient = MinioClient.builder()
            .endpoint("https://your-minio-server.com")
            .credentials("ACCESS_KEY", "SECRET_KEY")
            .build();
    }

    @GetMapping("/api/getFileUrl")
    public String getFileUrl(@RequestParam String bucketName, @RequestParam String objectName) {
        try {
            // 生成预签名 URL(有效期 1 小时)
            String url = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(60 * 60) // 有效期:1小时
                .build()
            );
            return url;
        } catch (Exception e) {
            e.printStackTrace();
            return "Error generating URL";
        }
    }
}

1.2 接口返回示例

前端调用 /api/getFileUrl,后端返回:

{
  "fileUrl": "https://your-minio-server.com/bucket-name/object-name?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=..."
}

优点:

  • 预签名 URL 确保文件只能在指定时间内访问,过期后失效。
  • URL 绑定文件路径和权限,防止未授权访问。

缺点:

  • 不适合需要频繁访问多个文件的场景。
  • 每次访问都需要重新生成 URL,增加后端负载。
  • 仅适用于单次访问的场景。

实现方法 2:基于临时凭证(STS)

2.1 动态生成临时凭证

MinIO 支持通过 STS(Security Token Service)生成短时有效的临时访问凭证。

Java 生成临时凭证示例:

import io.minio.MinioClient;
import io.minio.credentials.AssumeRoleProvider;
import io.minio.credentials.Provider;

public class StsTokenService {

    private final MinioClient minioClient;

    public StsTokenService() {
        this.minioClient = MinioClient.builder()
                .endpoint("https://your-minio-server.com")
                .credentials("ACCESS_KEY", "SECRET_KEY")
                .build();
    }

    public Provider getTemporaryToken() throws Exception {
        AssumeRoleProvider provider = AssumeRoleProvider.builder()
                .client(minioClient)
                .durationSeconds(3600) // 临时凭证有效期
                .build();
        return provider;
    }
}

2.2 返回临时凭证

在接口中返回包含 AccessKey, SecretKeySessionToken 的临时凭证给前端。

接口返回示例:

{
  "accessKey": "temp-access-key",
  "secretKey": "temp-secret-key",
  "sessionToken": "temp-session-token",
  "bucketName": "your-bucket-name",
  "region": "your-region"
}

2.3 前端 SDK 使用临时凭证

前端使用临时凭证通过 SDK 访问 MinIO 文件。

JavaScript 示例:

const minioClient = new Minio.Client({
  endPoint: 'your-minio-server.com',
  accessKey: 'temp-access-key',
  secretKey: 'temp-secret-key',
  sessionToken: 'temp-session-token'
});

minioClient.getObject('your-bucket-name', 'object-name', function(err, dataStream) {
  if (err) {
    return console.log(err);
  }
  dataStream.pipe(fs.createWriteStream('downloaded-file.jpg'));
});

优点:

  • 临时凭证可以限制权限和有效期,提升安全性。
  • 适用于需要频繁访问多个文件的场景。
  • 前端可以直接使用 SDK 进行文件操作。

缺点:

  • 临时凭证泄露风险较高,需妥善管理。
  • 增加了前端实现的复杂度。
  • 需要处理凭证的刷新和过期问题。
  • 不适合只需单次访问的场景。

实现方法 3:后端代理文件请求

如果不想直接暴露文件路径或生成签名 URL,可以通过后端接口代理文件的下载请求。

Spring Boot 示例:

@GetMapping("/api/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String bucketName, @RequestParam String objectName) {
    try {
        InputStream fileStream = minioClient.getObject(
            GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build()
        );

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + objectName)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(fileStream));
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

前端访问 /api/download,后端完成文件下载逻辑,同时可以控制访问权限。

优点:

  • 完全控制文件访问权限。
  • 不暴露任何 MinIO 相关信息。
  • 适用于需要复杂权限控制的场景。

缺点:

  • 增加了后端负载,可能影响性能。
  • 需要处理大文件传输的效率问题。