View a markdown version of this page

使用 Amazon S3 預先簽章URLs - AWS SDK for Java 2.x

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 Amazon S3 預先簽章URLs

預先簽章URLs 提供私有 S3 物件的暫時存取權,而不需要使用者擁有 AWS 登入資料或許可。

例如,假設 Alice 可以存取 S3 物件,而且她想要暫時與 Bob 共用該物件的存取權。Alice 可以產生預先簽章的 GET 請求來與 Bob 共用,以便他可以下載物件,而無需存取 Alice 的登入資料。您可以為 HTTP GET 產生預先簽章URLs,並為 HTTP PUT 請求產生預先簽章的 URL。

產生物件的預先簽章 URL,然後下載它 (GET 請求)

下列範例包含兩個部分。

  • 第 1 部分:Alice 會產生物件的預先簽章 URL。

  • 第 2 部分:Bob 使用預先簽章的 URL 下載物件。

第 1 部分:產生 URL

Alice 在 S3 儲存貯體中已有物件。她使用以下程式碼來產生可在後續 GET 請求中使用的 URL 字串。

import com.example.s3.util.PresignUrlUtils; import org.slf4j.Logger; 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.GetObjectRequest; import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import software.amazon.awssdk.utils.IoUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; 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.file.Paths; import java.time.Duration; import java.util.UUID;
/* Create a pre-signed URL to download an object in a subsequent GET request. */ public String createPresignedGetUrl(String bucketName, String keyName) { try (S3Presigner presigner = S3Presigner.create()) { GetObjectRequest objectRequest = GetObjectRequest.builder() .bucket(bucketName) .key(keyName) .build(); GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() .signatureDuration(Duration.ofMinutes(10)) // The URL will expire in 10 minutes. .getObjectRequest(objectRequest) .build(); PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest); logger.info("Presigned URL: [{}]", presignedRequest.url().toString()); logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method()); return presignedRequest.url().toExternalForm(); } }

第 2 部分:下載 物件

Bob 使用下列其中一個程式碼選項來下載物件。或者,他可以使用瀏覽器來執行 GET 請求。

/* Use the JDK HttpURLConnection (since v1.1) class to do the download. */ public byte[] useHttpUrlConnectionToGet(String presignedUrlString) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array. try { URL presignedUrl = new URL(presignedUrlString); HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection(); connection.setRequestMethod("GET"); // Download the result of executing the request. try (InputStream content = connection.getInputStream()) { IoUtils.copy(content, byteArrayOutputStream); } logger.info("HTTP response code is " + connection.getResponseCode()); } catch (S3Exception | IOException e) { logger.error(e.getMessage(), e); } return byteArrayOutputStream.toByteArray(); }
/* Use the JDK HttpClient (since v11) class to do the download. */ public byte[] useHttpClientToGet(String presignedUrlString) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array. HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); HttpClient httpClient = HttpClient.newHttpClient(); try { URL presignedUrl = new URL(presignedUrlString); HttpResponse<InputStream> response = httpClient.send(requestBuilder .uri(presignedUrl.toURI()) .GET() .build(), HttpResponse.BodyHandlers.ofInputStream()); IoUtils.copy(response.body(), byteArrayOutputStream); logger.info("HTTP response code is " + response.statusCode()); } catch (URISyntaxException | InterruptedException | IOException e) { logger.error(e.getMessage(), e); } return byteArrayOutputStream.toByteArray(); }
/* Use the AWS SDK for Java SdkHttpClient class to do the download. */ public byte[] useSdkHttpClientToGet(String presignedUrlString) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array. try { URL presignedUrl = new URL(presignedUrlString); SdkHttpRequest request = SdkHttpRequest.builder() .method(SdkHttpMethod.GET) .uri(presignedUrl.toURI()) .build(); HttpExecuteRequest executeRequest = HttpExecuteRequest.builder() .request(request) .build(); try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) { HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call(); response.responseBody().ifPresentOrElse( abortableInputStream -> { try { IoUtils.copy(abortableInputStream, byteArrayOutputStream); } catch (IOException e) { throw new RuntimeException(e); } }, () -> logger.error("No response body.")); logger.info("HTTP Response code is {}", response.httpResponse().statusCode()); } } catch (URISyntaxException | IOException e) { logger.error(e.getMessage(), e); } return byteArrayOutputStream.toByteArray(); }

在 上 AWS SDK for Java 2.x 提供預先簽章的 URL 擴充功能S3AsyncClient,透過 SDK 非同步管道使用預先簽章的 URL 下載物件。使用 存取擴充功能S3AsyncClient.presignedUrlExtension()。此功能適用於 SDK 2.48.0 版及更新版本。

CRT 用戶端限制

AWS CRT 型 S3 用戶端 (S3AsyncClient.crtBuilder()) 目前不支援預先簽章的 URL 延伸。請改用 S3AsyncClient.builder()

檢查總和模式需求

若要驗證資料完整性,請在產生預先簽章的 URL GetObjectRequest時,在 checksumMode(ChecksumMode.ENABLED)上設定 。在預先簽署時間指定檢查總和模式。您無法在稍後執行下載時新增它。當您使用S3AsyncClient預先簽章的 URL 延伸時,軟體開發套件會自動傳送所需的x-amz-checksum-mode標頭。

import java.nio.file.Paths; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
下載至檔案或位元組
Download to file
S3AsyncClient s3AsyncClient = S3AsyncClient.builder() .region(Region.US_EAST_1) .build(); GetObjectResponse response = s3AsyncClient.presignedUrlExtension() .getObject( PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build(), Paths.get("/tmp/downloaded-file.bin")) .join(); // Or use the consumer builder pattern GetObjectResponse response = s3AsyncClient.presignedUrlExtension() .getObject( r -> r.presignedUrl(presignedGetObjectRequest.url()), Paths.get("/tmp/downloaded-file.bin")) .join();
Download to bytes
S3AsyncClient s3AsyncClient = S3AsyncClient.builder() .region(Region.US_EAST_1) .build(); ResponseBytes<GetObjectResponse> bytes = s3AsyncClient.presignedUrlExtension() .getObject( PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build(), AsyncResponseTransformer.toBytes()) .join(); // Or use the consumer builder pattern ResponseBytes<GetObjectResponse> bytes = s3AsyncClient.presignedUrlExtension() .getObject( r -> r.presignedUrl(presignedGetObjectRequest.url()), AsyncResponseTransformer.toBytes()) .join();
大型檔案的分段下載

若要平行下載大型物件,請在用戶端multipartEnabled(true)上設定 :

Download to file
S3AsyncClient multipartClient = S3AsyncClient.builder() .region(Region.US_EAST_1) .multipartEnabled(true) .multipartConfiguration(c -> c.minimumPartSizeInBytes(8 * 1024 * 1024L)) .build(); GetObjectResponse response = multipartClient.presignedUrlExtension() .getObject( PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build(), Paths.get("/tmp/large-file.bin")) .join();
Download to bytes
S3AsyncClient multipartClient = S3AsyncClient.builder() .region(Region.US_EAST_1) .multipartEnabled(true) .multipartConfiguration(c -> c.minimumPartSizeInBytes(8 * 1024 * 1024L)) .build(); ResponseBytes<GetObjectResponse> bytes = multipartClient.presignedUrlExtension() .getObject( PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build(), AsyncResponseTransformer.toBytes()) .join();
使用 Amazon S3 Transfer Manager

透過 Amazon S3 Transfer Manager,您可以使用預先簽章URLs 透過 進行進度追蹤來下載物件TransferListener

s3-transfer-manager成品新增至您的專案:

<dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.48.01</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3-transfer-manager</artifactId> </dependency> </dependencies>

1Maven Central Repository 上尋找最新的 BOM 版本

import java.nio.file.Paths; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest; import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.model.CompletedFileDownload; import software.amazon.awssdk.transfer.s3.model.Download; import software.amazon.awssdk.transfer.s3.model.PresignedDownloadFileRequest; import software.amazon.awssdk.transfer.s3.model.PresignedDownloadRequest; import software.amazon.awssdk.transfer.s3.model.PresignedFileDownload; import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
Download to file
S3TransferManager transferManager = S3TransferManager.create(); PresignedFileDownload fileDownload = transferManager.downloadFileWithPresignedUrl( PresignedDownloadFileRequest.builder() .presignedUrlDownloadRequest(PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build()) .destination(Paths.get("/tmp/transfer-download.bin")) .addTransferListener(LoggingTransferListener.create()) .build()); CompletedFileDownload completed = fileDownload.completionFuture().join();
Download to bytes
S3TransferManager transferManager = S3TransferManager.create(); Download<ResponseBytes<GetObjectResponse>> bytesDownload = transferManager.downloadWithPresignedUrl( PresignedDownloadRequest.<ResponseBytes<GetObjectResponse>>builder() .presignedUrlDownloadRequest(PresignedUrlDownloadRequest.builder() .presignedUrl(presignedGetObjectRequest.url()) .build()) .responseTransformer(AsyncResponseTransformer.toBytes()) .addTransferListener(LoggingTransferListener.create()) .build()); ResponseBytes<GetObjectResponse> result = bytesDownload.completionFuture().join().result();
不支援暫停和繼續

與一般 不同FileDownloadPresignedFileDownload 不支援暫停和繼續,因為預先簽章URLs 可能會在暫停傳輸期間過期。

如需 Amazon S3 Transfer Manager 的詳細資訊,包括組態選項和其他範例,請參閱 使用 Amazon S3 Transfer Manager 傳輸檔案和目錄

請參閱 GitHub 上的完整範例測試

產生上傳的預先簽章 URL,然後上傳檔案 (PUT 請求)

下列範例包含兩個部分。

  • 第 1 部分:Alice 會產生預先簽章的 URL 來上傳物件。

  • 第 2 部分:Bob 使用預先簽章的 URL 上傳檔案。

第 1 部分:產生 URL

Alice 已有 S3 儲存貯體。她使用以下程式碼來產生可在後續 PUT 請求中使用的 URL 字串。

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;
/* 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(); } }

第 2 部分:上傳檔案物件

Bob 使用下列三個程式碼選項之一來上傳檔案。

/* 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); } }
/* 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); } }
/* 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); } }

請參閱 GitHub 上的完整範例測試