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

S3预签名URL出现SignatureDoesNotMatch错误求助

AWS S3预签名URL POST请求签名不匹配问题

我的AWS S3存储桶内有一个名为items/的文件夹,用于存放所有图片。生成预签名URL时已在key中正确指定该路径,但向该URL发送POST请求时出现错误:

"The request signature we calculated does not match the signature you provided. Check your key and signing method."

相关代码实现如下:

@Configuration
public class S3Config {

    @Value("${aws.region}")
    private String awsRegion;

    @Bean
    public S3Client s3Client() {
        S3Client client = S3Client.builder()
                .region(Region.of(awsRegion))
                .build();
        return client;
    }

    @Bean
    public S3Presigner s3Presigner() {
        return S3Presigner.builder()
                .region(Region.of(awsRegion))
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build();
    }

}
@RestController
@RequestMapping("/s3")
public class S3Controller {

    private final S3Service s3Service;

    @Autowired
    public S3Controller(S3Service s3Service) {
        this.s3Service = s3Service;
    }

    // Endpoint to generate a presigned URL for PUT operations
    @GetMapping("/presign-put/{filename}")
    public ResponseEntity<String> getPresignedPutUrl(@PathVariable String filename) {
        try {
            String presignedUrl = s3Service.generatePresignedUrl(filename);
            return ResponseEntity.ok(presignedUrl);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to generate presigned URL: " + e.getMessage());
        }
    }
}
@Service
public class S3Service {
    private final S3Client s3;
    private final S3Presigner presigner;

    @Value("${aws.s3.bucketName}") //defined inside of application properties
    private String bucketName;

    public S3Service(S3Client s3, S3Presigner s3Presigner) {
        this.s3 = s3;
        this.presigner = s3Presigner;
    }

    public String generatePresignedUrl(String filename) {
        String key = "items/" + filename;
        PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                .bucket(bucketName)
                .key(key)
                .build();

        PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
                .putObjectRequest(putObjectRequest)
                .signatureDuration(Duration.ofHours(24))
                .build();

        PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
        return presignedRequest.url().toString();
    }
}

问题核心原因

你生成的是PUT操作的预签名URL,但实际发送的是POST请求。S3的签名算法与HTTP请求方法强绑定,方法不匹配会直接导致签名验证失败。

修复方案

方案一:客户端改用PUT请求(最简方案)

调整HTTP客户端的请求方法为PUT,匹配预签名URL的生成类型。例如用curl测试:

curl -X PUT -T "本地文件路径" "生成的预签名URL"

方案二:服务端生成POST预签名表单(若必须用POST)

如果业务要求必须用POST请求,需要修改服务端代码生成POST上传的预签名表单(S3 POST预签名逻辑与PUT不同,需生成表单字段而非单纯URL):

修改S3Service中的方法:

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import software.amazon.awssdk.services.s3.model.PostPolicy;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPostRequest;

public Map<String, String> generatePresignedPostUrl(String filename) {
    String key = "items/" + filename;
    Date expiration = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);

    PostPolicy policy = PostPolicy.builder(bucketName, expiration)
            .addCondition(KeyCondition.equals(key))
            .build();

    PresignedPostRequest presignedPost = presigner.presignPost(policy);

    Map<String, String> formFields = new HashMap<>();
    formFields.put("url", presignedPost.url().toString());
    formFields.putAll(presignedPost.fields());

    return formFields;
}

修改控制器返回表单信息:

@GetMapping("/presign-post/{filename}")
public ResponseEntity<Map<String, String>> getPresignedPostUrl(@PathVariable String filename) {
    try {
        Map<String, String> presignedPostData = s3Service.generatePresignedPostUrl(filename);
        return ResponseEntity.ok(presignedPostData);
    } catch (Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("error", "Failed to generate presigned POST data: " + e.getMessage()));
    }
}

客户端需用POST方法提交表单,包含返回的所有字段和文件(文件字段名通常为file)。

额外排查点

  • 确认生成预签名URL的凭证与S3验证时使用的凭证一致,避免多凭证混用。
  • 确保请求时未修改URL中的任何参数(如签名、过期时间)。
  • 检查文件名是否包含特殊字符,必要时进行URL编码。

内容的提问来源于stack exchange,提问作者Ariya nazari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:41:13