You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Java+Spring Boot实现AWS S3 presign URL的分片上传授权?

实现S3分片上传的预签名URL生成(Java+Spring Boot)

S3分片上传需分三个核心步骤完成,每个步骤都需要对应权限的预签名URL。以下是基于AWS SDK for Java v2(推荐使用,v1已停止维护)的完整实现方案:

前置准备

  • 在Spring Boot项目中引入AWS S3 SDK v2依赖
  • 配置AWS凭证:可通过环境变量、IAM角色(ECS/EKS部署场景)或application.yml配置访问密钥(生产环境优先使用IAM角色)

代码实现

1. 依赖配置(pom.xml)

<dependencies>
    <!-- AWS S3 SDK v2 -->
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3</artifactId>
        <version>2.25.0</version>
    </dependency>
    <!-- Spring Boot基础依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

2. S3客户端配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class S3Config {

    @Bean
    public S3Client s3Client() {
        return S3Client.builder()
                .region(Region.US_EAST_1) // 替换为你的存储桶所在区域
                .build();
    }
}

3. 预签名URL生成服务

该服务封装了分片上传三个核心步骤的预签名逻辑:

import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.*;
import java.time.Duration;
import java.util.Map;

@Service
public class S3MultipartPresignService {

    private final S3Client s3Client;

    public S3MultipartPresignService(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    // 初始化分片上传,返回唯一uploadId和对象键
    public Map<String, String> initiateMultipartUpload(String bucketName, String objectKey) {
        CreateMultipartUploadRequest request = CreateMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(objectKey)
                .build();

        CreateMultipartUploadResponse response = s3Client.createMultipartUpload(request);
        return Map.of("uploadId", response.uploadId(), "objectKey", objectKey);
    }

    // 生成单个分片的预签名上传URL(仅允许上传指定分片)
    public String generatePresignedUploadPartUrl(String bucketName, String objectKey, String uploadId, int partNumber, Duration expiration) {
        try (S3Presigner presigner = S3Presigner.create()) {
            UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(objectKey)
                    .uploadId(uploadId)
                    .partNumber(partNumber)
                    .build();

            UploadPartPresignRequest presignRequest = UploadPartPresignRequest.builder()
                    .signatureDuration(expiration)
                    .uploadPartRequest(uploadPartRequest)
                    .build();

            return presigner.presignUploadPart(presignRequest).url().toString();
        }
    }

    // 生成完成分片上传的预签名URL(用于提交所有分片的ETag信息)
    public String generatePresignedCompleteMultipartUrl(String bucketName, String objectKey, String uploadId, CompletedMultipartUpload completedMultipart, Duration expiration) {
        try (S3Presigner presigner = S3Presigner.create()) {
            CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(objectKey)
                    .uploadId(uploadId)
                    .multipartUpload(completedMultipart)
                    .build();

            CompleteMultipartUploadPresignRequest presignRequest = CompleteMultipartUploadPresignRequest.builder()
                    .signatureDuration(expiration)
                    .completeMultipartUploadRequest(completeRequest)
                    .build();

            return presigner.presignCompleteMultipartUpload(presignRequest).url().toString();
        }
    }
}

4. 对外接口控制器

提供REST接口供第二个应用调用,获取各环节的预签名URL:

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import software.amazon.awssdk.services.s3.model.CompletedMultipartUpload;
import software.amazon.awssdk.services.s3.model.CompletedPart;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/s3/presign")
public class S3PresignController {

    private final S3MultipartPresignService presignService;

    public S3PresignController(S3MultipartPresignService presignService) {
        this.presignService = presignService;
    }

    // 初始化分片上传
    @PostMapping("/initiate")
    public ResponseEntity<Map<String, String>> initiateMultipartUpload(@RequestParam String bucketName, @RequestParam String objectKey) {
        Map<String, String> result = presignService.initiateMultipartUpload(bucketName, objectKey);
        return ResponseEntity.ok(result);
    }

    // 获取单个分片的预签名上传URL
    @GetMapping("/upload-part")
    public ResponseEntity<String> getUploadPartPresignUrl(
            @RequestParam String bucketName,
            @RequestParam String objectKey,
            @RequestParam String uploadId,
            @RequestParam int partNumber,
            @RequestParam(defaultValue = "3600") int expirationSeconds) {
        String presignUrl = presignService.generatePresignedUploadPartUrl(
                bucketName,
                objectKey,
                uploadId,
                partNumber,
                Duration.ofSeconds(expirationSeconds)
        );
        return ResponseEntity.ok(presignUrl);
    }

    // 获取完成分片上传的预签名URL
    @PostMapping("/complete")
    public ResponseEntity<String> getCompleteMultipartPresignUrl(
            @RequestParam String bucketName,
            @RequestParam String objectKey,
            @RequestParam String uploadId,
            @RequestBody List<CompletedPartDto> completedParts,
            @RequestParam(defaultValue = "3600") int expirationSeconds) {

        List<CompletedPart> parts = completedParts.stream()
                .map(dto -> CompletedPart.builder()
                        .partNumber(dto.getPartNumber())
                        .eTag(dto.getETag())
                        .build())
                .collect(Collectors.toList());

        CompletedMultipartUpload completedMultipart = CompletedMultipartUpload.builder()
                .parts(parts)
                .build();

        String presignUrl = presignService.generatePresignedCompleteMultipartUrl(
                bucketName,
                objectKey,
                uploadId,
                completedMultipart,
                Duration.ofSeconds(expirationSeconds)
        );
        return ResponseEntity.ok(presignUrl);
    }

    // 辅助DTO:接收分片上传后的ETag和分片号信息
    public static class CompletedPartDto {
        private int partNumber;
        private String eTag;

        // Getter & Setter
        public int getPartNumber() { return partNumber; }
        public void setPartNumber(int partNumber) { this.partNumber = partNumber; }
        public String getETag() { return eTag; }
        public void setETag(String eTag) { this.eTag = eTag; }
    }
}

权限控制关键要点

  • 最小权限原则:生成预签名URL的IAM主体(用户/角色)仅需授予以下权限,并限定到具体存储桶和对象前缀:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:CreateMultipartUpload",
                "s3:UploadPart",
                "s3:CompleteMultipartUpload",
                "s3:AbortMultipartUpload" // 可选,允许取消未完成的上传,避免存储资源浪费
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket-name",
                "arn:aws:s3:::your-bucket-name/*"
            ]
        }
    ]
}
  • 过期时间控制:为预签名URL设置合理的过期时间(如1小时),降低URL被滥用的风险
  • 对象键唯一性:要求第二个应用使用唯一的对象键,避免覆盖存储桶中已有的文件

第二个应用的调用流程

  1. 调用POST /s3/presign/initiate接口,获取uploadId和objectKey
  2. 将待上传文件拆分为分片(S3要求最小分片大小为5MB,最后一片可小于5MB)
  3. 对每个分片调用GET /s3/presign/upload-part获取预签名URL,通过PUT请求上传分片
  4. 收集所有分片的ETag和分片号,调用POST /s3/presign/complete获取完成上传的预签名URL,通过POST请求提交完成操作

内容的提问来源于stack exchange,提问作者Artem Causelove

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:00:42