如何通过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被滥用的风险
- 对象键唯一性:要求第二个应用使用唯一的对象键,避免覆盖存储桶中已有的文件
第二个应用的调用流程
- 调用
POST /s3/presign/initiate接口,获取uploadId和objectKey - 将待上传文件拆分为分片(S3要求最小分片大小为5MB,最后一片可小于5MB)
- 对每个分片调用
GET /s3/presign/upload-part获取预签名URL,通过PUT请求上传分片 - 收集所有分片的ETag和分片号,调用
POST /s3/presign/complete获取完成上传的预签名URL,通过POST请求提交完成操作
内容的提问来源于stack exchange,提问作者Artem Causelove
相关产品推荐
相关产品推荐

