我遇到与附加自定义元数据信息的 PUT 预签名 url 相关的问题。
我的相关代码是:
public URL generatePushResourceLocation(DocumentId documentId, String contentMD5) {
String key = documentId.getId().toString();
PutObjectRequest.Builder putObjectRequestBuilder = PutObjectRequest.builder().bucket(bucket).key(key)
.metadata(Map.of("mk1", "mv1"));
PutObjectRequest putObjectRequest = putObjectRequestBuilder.build();
PutObjectPresignRequest request = PutObjectPresignRequest.builder().putObjectRequest(putObjectRequest)
.signatureDuration(Duration.ofMinutes(1)).build();
PresignedPutObjectRequest presignPutObject = s3Presigner.presignPutObject(request);
URL signedPutURL = presignPutObject.url();
log.info("Push Presigned URL created: [{}]", signedPutURL.toString());
return signedPutURL;
}
如您所见,我只是生成一个带有元数据的 put 预签名 url。
当我尝试使用这个预先签名的网址时,我得到:
我们计算的请求签名与您提供的签名不匹配。检查您的密钥和签名方法。
完整回复是:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<AWSAccessKeyId>AKIAQ3EGWRZYSIU7ENEO</AWSAccessKeyId>
<StringToSign>AWS4-HMAC-SHA256
20240402T122245Z
20240402/eu-west-1/s3/aws4_request
66cdf5260d90912bebfd2cc337f5c1bcfb8fb6b4847852bd427627f82bf9b8c8</StringToSign>
<SignatureProvided>bd7e26bf661eea50966733816872544a6bf75151e30f819f2239725fcae08203</SignatureProvided>
<StringToSignBytes>41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 32 30 32 34 30 34 30 32 54 31 32 32 32 34 35 5a 0a 32 30 32 34 30 34 30 32 2f 65 75 2d 77 65 73 74 2d 31 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 36 36 63 64 66 35 32 36 30 64 39 30 39 31 32 62 65 62 66 64 32 63 63 33 33 37 66 35 63 31 62 63 66 62 38 66 62 36 62 34 38 34 37 38 35 32 62 64 34 32 37 36 32 37 66 38 32 62 66 39 62 38 63 38</StringToSignBytes>
<CanonicalRequest>PUT
/espaidoc-des/1117beac-c2b4-414a-a518-1c4ac805644d
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAQ3EGWRZYSIU7ENEO%2F20240402%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20240402T122245Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host%3Bx-amz-meta-mk1
host:s3.eu-west-1.amazonaws.com
x-amz-meta-mk1:
host;x-amz-meta-mk1
UNSIGNED-PAYLOAD</CanonicalRequest>
<CanonicalRequestBytes>50 55 54 0a 2f 65 73 70 61 69 64 6f 63 2d 64 65 73 2f 31 31 31 37 62 65 61 63 2d 63 32 62 34 2d 34 31 34 61 2d 61 35 31 38 2d 31 63 34 61 63 38 30 35 36 34 34 64 0a 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d 41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 72 65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 51 33 45 47 57 52 5a 59 53 49 55 37 45 4e 45 4f 25 32 46 32 30 32 34 30 34 30 32 25 32 46 65 75 2d 77 65 73 74 2d 31 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d 41 6d 7a 2d 44 61 74 65 3d 32 30 32 34 30 34 30 32 54 31 32 32 32 34 35 5a 26 58 2d 41 6d 7a 2d 45 78 70 69 72 65 73 3d 36 30 30 26 58 2d 41 6d 7a 2d 53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 68 6f 73 74 25 33 42 78 2d 61 6d 7a 2d 6d 65 74 61 2d 6d 6b 31 0a 68 6f 73 74 3a 73 33 2e 65 75 2d 77 65 73 74 2d 31 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 78 2d 61 6d 7a 2d 6d 65 74 61 2d 6d 6b 31 3a 0a 0a 68 6f 73 74 3b 78 2d 61 6d 7a 2d 6d 65 74 61 2d 6d 6b 31 0a 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44</CanonicalRequestBytes>
<RequestId>6Q3P82ZWPWNAC5V0</RequestId>
<HostId>uEVaiZU1gKj0n6KNIrkuWHELwJD9El/DdQ1uKu+o5dwsEvoFdaVTv/gL0e6AfbN/kthrJlo0+9A=</HostId>
</Error>
我的curl命令:
curl --request PUT --header '内容类型:' --data-binary '@hurl/push-document/lorem.txt' 'https://s3.eu-west-1.amazonaws.com/espaidoc -des/54c07cd8-1bce-43b9-ab32-28a7e9108c19?X-Amz-算法=AWS4-HMAC-SHA256&X-Amz-Date=20240402T123532Z&X-Amz-SignedHeaders=主机%3Bx-amz-meta-mk1&X-Amz-Expires=60&X -Amz-Credential=AKIAQ3EGWRZYSIU7ENEO%2F20240402%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Signature=ebfbbcfbbaadae7b580ed0a6cddb503371a6aa11b59e229fcc5663225f45a9be'
有什么想法吗?
我刚刚运行了以下 AWS S3 代码,它运行良好。我使用的文件是我在 Java 程序中指定的名为 book.pdk 的 PDF。输出是:
2024-04-02 11:29:17 [main] INFO com.example.s3.util.PresignUrlUtils:20 - Bucket [b-e69cb1f5-e073-4724-b285-787f0951b193] created
2024-04-02 11:29:17 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:105 - Presigned URL to upload a file to: [https://b-xxxxxx5-787f0951b193.s3.amazonaws.com/k-30d2854d-8a89-4c40-9519-d7e14b77e6ad?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240402T152917Z&X-Amz-SignedHeaders=host%3Bx-amz-meta-author%3Bx-amz-meta-version&X-Amz-Credential=AKIA33JWY3BX2TYWHOOR%2F20240402%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Expires=600&X-Amz-Signature=cb1356a0afe7e565d64e8ac45603d5dfd4e535af285188600485268ebbe2a8ae]
2024-04-02 11:29:17 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:106 - HTTP method: [PUT]
2024-04-02 11:29:21 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:116 - Begin [C:\AWS\book.pdf] upload
2024-04-02 11:29:38 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:142 - HTTP response code is 200
2024-04-02 11:29:39 [main] INFO com.example.s3.util.PresignUrlUtils:36 - Object [k-30d2854d-8a89-4c40-9519-d7e14b77e6ad] deleted
2024-04-02 11:29:39 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:153 - Begin [C:\AWS\book.pdf] upload
2024-04-02 11:29:58 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:166 - HTTP response code is 200
2024-04-02 11:29:58 [main] INFO com.example.s3.util.PresignUrlUtils:36 - Object [k-30d2854d-8a89-4c40-9519-d7e14b77e6ad] deleted
2024-04-02 11:29:58 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:177 - Begin [C:\AWS\book.pdf] upload
2024-04-02 11:30:17 [main] INFO com.example.s3.GeneratePresignedUrlAndPutFileWithMetadata:197 - Response code: 200
2024-04-02 11:30:18 [main] INFO com.example.s3.util.PresignUrlUtils:36 - Object [k-30d2854d-8a89-4c40-9519-d7e14b77e6ad] deleted
2024-04-02 11:30:18 [main] INFO com.example.s3.util.PresignUrlUtils:28 - Bucket [b-e69cb1f5-e073-4724-b285-787f0951b193] deleted
Java V2 代码在这里:
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.import]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
* <p>
* For more information, see the following documentation topic:
* <p>
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class GeneratePresignedUrlAndPutFileWithMetadata {
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(GeneratePresignedUrlAndPutFileWithMetadata.class);
private final static S3Client s3Client = S3Client.create();
public static void main(String[] args) {
String bucketName = "b-" + UUID.randomUUID();
String keyName = "k-" + UUID.randomUUID();
// Uncomment the following two lines and comment out the previous two lines to use an image file instead of a PDF file.
//String resourcePath = "image.png";
//String contentType = "image/png";
Map<String, String> metadata = Map.of(
"author", "Bob",
"version", "1.0.0.0"
);
PresignUrlUtils.createBucket(bucketName, s3Client);
GeneratePresignedUrlAndPutFileWithMetadata presign = new GeneratePresignedUrlAndPutFileWithMetadata();
try {
String presignedUrlString = presign.createPresignedUrl(bucketName, keyName, metadata);
File myFile = new File("C:\\AWS\\book.pdf");
presign.useHttpUrlConnectionToPut(presignedUrlString, myFile, metadata);
PresignUrlUtils.deleteObject(bucketName, keyName, s3Client);
presign.useHttpClientToPut(presignedUrlString, myFile, metadata);
PresignUrlUtils.deleteObject(bucketName, keyName, s3Client);
presign.useSdkHttpClientToPut(presignedUrlString, myFile, metadata);
} finally {
PresignUrlUtils.deleteObject(bucketName, keyName, s3Client);
PresignUrlUtils.deleteBucket(bucketName, s3Client);
}
}
// snippet-start:[presigned.java2.generatepresignedurlandputfilewithmetadata.main]
// snippet-start:[presigned.java2.generatepresignedurlandputfilewithmetadata.createpresignedurl]
/* Create a presigned URL to use in a subsequent PUT request */
public String createPresignedUrl(String bucketName, String keyName, Map<String, String> metadata) {
try (S3Presigner presigner = S3Presigner.create()) {
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(keyName)
.metadata(metadata)
.build();
PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(10)) // The URL expires in 10 minutes.
.putObjectRequest(objectRequest)
.build();
PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
String myURL = presignedRequest.url().toString();
logger.info("Presigned URL to upload a file to: [{}]", myURL);
logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());
return presignedRequest.url().toExternalForm();
}
}
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.createpresignedurl]
// snippet-start:[presigned.java2.generatepresignedurlandputfilewithmetadata.basichttpclient]
/* Use the JDK HttpURLConnection (since v1.1) class to do the upload. */
public void useHttpUrlConnectionToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
logger.info("Begin [{}] upload", fileToPut.toString());
try {
URL presignedUrl = new URL(presignedUrlString);
HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
connection.setDoOutput(true);
metadata.forEach((k, v) -> connection.setRequestProperty("x-amz-meta-" + k, v));
connection.setRequestMethod("PUT");
OutputStream out = connection.getOutputStream();
try (RandomAccessFile file = new RandomAccessFile(fileToPut, "r");
FileChannel inChannel = file.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(8192); //Buffer size is 8k
while (inChannel.read(buffer) > 0) {
buffer.flip();
for (int i = 0; i < buffer.limit(); i++) {
out.write(buffer.get());
}
buffer.clear();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
out.close();
connection.getResponseCode();
logger.info("HTTP response code is " + connection.getResponseCode());
} catch (S3Exception | IOException e) {
logger.error(e.getMessage(), e);
}
}
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.basichttpclient]
// snippet-start:[presigned.java2.generatepresignedurlandputfilewithmetadata.jdkhttpclient]
/* Use the JDK HttpClient (since v11) class to do the upload. */
public void useHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
logger.info("Begin [{}] upload", fileToPut.toString());
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
metadata.forEach((k, v) -> requestBuilder.header("x-amz-meta-" + k, v));
HttpClient httpClient = HttpClient.newHttpClient();
try {
final HttpResponse<Void> response = httpClient.send(requestBuilder
.uri(new URL(presignedUrlString).toURI())
.PUT(HttpRequest.BodyPublishers.ofFile(Path.of(fileToPut.toURI())))
.build(),
HttpResponse.BodyHandlers.discarding());
logger.info("HTTP response code is " + response.statusCode());
} catch (URISyntaxException | InterruptedException | IOException e) {
logger.error(e.getMessage(), e);
}
}
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.jdkhttpclient]
// snippet-start:[presigned.java2.generatepresignedurlandputfilewithmetadata.sdkhttpclient]
/* Use the AWS SDK for Java V2 SdkHttpClient class to do the upload. */
public void useSdkHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
logger.info("Begin [{}] upload", fileToPut.toString());
try {
URL presignedUrl = new URL(presignedUrlString);
SdkHttpRequest.Builder requestBuilder = SdkHttpRequest.builder()
.method(SdkHttpMethod.PUT)
.uri(presignedUrl.toURI());
// Add headers
metadata.forEach((k, v) -> requestBuilder.putHeader("x-amz-meta-" + k, v));
// Finish building the request.
SdkHttpRequest request = requestBuilder.build();
HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
.request(request)
.contentStreamProvider(new FileContentStreamProvider(fileToPut.toPath()))
.build();
try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
logger.info("Response code: {}", response.httpResponse().statusCode());
}
} catch (URISyntaxException | IOException e) {
logger.error(e.getMessage(), e);
}
}
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.sdkhttpclient]
// snippet-end:[presigned.java2.generatepresignedurlandputfilewithmetadata.main]
}