AWS S3预签名URL上传签名不匹配,Java SDK生成URL缺失KMS参数
S3预签名URL上传签名不匹配&Java SDK不返回KMS参数问题解决
调用S3预签名URL执行上传时出现报错:The request signature we calculated does not match the signature you provided. Check your key and signing method。
可正常工作的NodeJS SDK配置
Node依赖:"aws-sdk": "^2.873.0"
生成预签名URL代码:
let params = { Bucket: s3_credentials.s3Bucket, Key: s3key, ContentType: contentType, ServerSideEncryption: 'aws:kms', SSEKMSKeyId: s3_credentials.kmsKeyName, Expires: 500 // 过期时间单位为秒 }; const signedUrl = s3.getSignedUrl('putObject', params);
生成的URL会自动包含Content-Type、x-amz-server-side-encryption、x-amz-server-side-encryption-aws-kms-key-id参数,直接调用即可上传成功。
存在问题的Java SDK配置
Maven依赖:
<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-sdk-java</artifactId> <version>2.17.66</version> </dependency>
现有生成预签名URL代码:
PutObjectRequest objectRequest = PutObjectRequest.builder() .bucket(SPEC_BUCKET_NAME) .key(s3Key) .contentType(contentType) .serverSideEncryption(ServerSideEncryption.AWS_KMS) .ssekmsKeyId(getKmsId()) .expires(expiration) .build(); PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder().putObjectRequest(objectRequest).signatureDuration(Duration.ofMinutes(60)).build(); PresignedPutObjectRequest presignedRequestResponse = s3Presigner.presignPutObject(presignRequest); String presignedUrl = presignedRequestResponse.url().toString();
KMS ID获取代码:
private String getKmsId() { KmsClient kmsClient = KmsClient.builder().build(); DescribeKeyRequest req = DescribeKeyRequest.builder().keyId("alias/xxx").build(); DescribeKeyResponse res = kmsClient.describeKey(req); logger.info(res.keyMetadata().toString()); return res.keyMetadata().keyId(); }
也尝试过直接传入KMS别名,问题依旧没有解决:
.serverSideEncryption(ServerSideEncryption.AWS_KMS) .ssekmsKeyId("alias/xxxx")
问题原因&解决方案
Java SDK v2的默认行为和NodeJS SDK v2不同:Java SDK不会自动把服务端加密、Content-Type这类配置参数放到预签名URL的查询串中,只会将这些参数纳入签名计算逻辑,要求调用方在上传时主动在请求头带上对应参数,否则就会出现签名不匹配的报错。
可根据业务场景选择以下两种解决方式:
- 方式一:上传时主动携带请求头
调用生成的预签名URL发起PUT请求时,主动在请求头中加入Content-Type、x-amz-server-side-encryption: aws:kms、x-amz-server-side-encryption-aws-kms-key-id: 你的KMS ID三个参数,和生成预签名URL时的配置完全一致即可,不需要修改生成预签名URL的代码。 - 方式二:强制将加密参数写入预签名URL
如果上传端无法自定义请求头,可以在生成预签名URL时显式指定需要签名的头部,SDK会自动将这些头部转换为URL查询参数,修改后代码如下:
修改后生成的预签名URL会和NodeJS生成的结构一致,直接调用即可完成上传。PutObjectRequest objectRequest = PutObjectRequest.builder() .bucket(SPEC_BUCKET_NAME) .key(s3Key) .contentType(contentType) .serverSideEncryption(ServerSideEncryption.AWS_KMS) .ssekmsKeyId(getKmsId()) .build(); PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder() .putObjectRequest(objectRequest) .signatureDuration(Duration.ofMinutes(60)) // 显式指定需要加入URL的签名参数 .putSignedHeader("x-amz-server-side-encryption", ServerSideEncryption.AWS_KMS.toString()) .putSignedHeader("x-amz-server-side-encryption-aws-kms-key-id", getKmsId()) .putSignedHeader("Content-Type", contentType) .build(); PresignedPutObjectRequest presignedRequestResponse = s3Presigner.presignPutObject(presignRequest); String presignedUrl = presignedRequestResponse.url().toString();
额外注意事项
- 生成预签名URL的凭证和上传时使用的凭证必须一致,且需要具备
s3:PutObject、kms:GenerateDataKey权限 - KMS密钥策略需要允许对应凭证调用密钥,跨账号场景需要额外配置跨账号访问权限
- 生成预签名URL时的ContentType和上传时的ContentType必须完全一致,任意偏差都会触发签名错误
内容的提问来源于stack exchange,提问作者mgnfcnt
相关产品推荐
相关产品推荐

