Reputation: 2052
I'm using the following two JAVA
methods to create a signed URL
to upload to Google Cloud Storage, using HTTP PUT
. The first method is supposed to generate the actual upload URL using POST
, while the second one is supposed to generate the URL
using serviceAccountCredentials
, to be used(to POST
) by the first one.
First Method
public String getUploadLink(String bucketName, String uuid, String objectName, String mimeType)
throws IOException, GenericAttachmentException {
if (!bucketExists(bucketName)) {
createBucket(bucketName);
}
URL myURL = new URL(getSignedUrlToPost(bucketName, uuid, objectName, mimeType));
HttpURLConnection myURLConnection = (HttpURLConnection) myURL.openConnection();
myURLConnection.setRequestMethod("POST");
myURLConnection.setRequestProperty("Content-Type", mimeType);
myURLConnection.setRequestProperty("x-goog-resumable", "start");
// Send POST request
myURLConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(myURLConnection.getOutputStream());
wr.flush();
wr.close();
int responseCode = myURLConnection.getResponseCode();
if (responseCode != 201) {
throw new GenericAttachmentException(500,
"Error generating signed URL",
"Something went wrong while attempting to generate the URL.");
}
return myURLConnection.getHeaderField("Location");
}
Second Method
private String getSignedUrlToPost(String bucketName, String uuid, String objectName,
String mimeType) throws GenericAttachmentException {
try {
String verb = "POST";
long now = System.currentTimeMillis();
/* Expire in a minute. */
long expiryTimeInSeconds = (now + 60 * 1000L) / 1000;
String canonicalizedExtensionHeaders = "x-goog-resumable:start";
byte[] sr = serviceAccountCredentials.sign(
(verb + "\n\n" + mimeType + "\n" + expiryTimeInSeconds + "\n" + canonicalizedExtensionHeaders
+
"\n" + "/" + bucketName + "/" + uuid + "/" + objectName).getBytes());
String urlSignature = new String(Base64.encodeBase64(sr));
return "https://storage.googleapis.com/" + bucketName + "/" + uuid + "/" + objectName +
"?GoogleAccessId=" + serviceAccountEmail +
"&Expires=" + expiryTimeInSeconds +
"&Signature=" + URLEncoder.encode(urlSignature, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new GenericAttachmentException(500,
"Something went wrong while encoding the URL.",
e.getMessage());
}
}
This gives me an upload URL
, as follows:
https://storage.googleapis.com/bucket-name/7c9a5bd6-ece2-497d-b485-a9c53e27f253/a.pdf?GoogleAccessId=storage-dev@project-name-xxxxxx.iam.gserviceaccount.com&Expires=1592883655&Signature=IlTGvwGNN8VYrPE9qzSW0AIAwqMvbNoZ34TQ4nr4Po5vwZx78or9iiqBhO0jqoeoX6BYP%2BHGkWPIKMUijB%2FZ0L6Z%2BtnaZZkIJ581YQ3JK8BEHWqWyf0V07RwAN0TGAyld7h1JntWmGDyXKtjmy6Skt1C0GocJZA2x9GMxo94OD9kpFbjBucixgQDE%2BEtCzDUXWkymATls690pyLftXhAI0CVWg%2FPlcAe2Q%2F9M%2F68s5eWVSXa0%2BXIVQQ%2FucgXO8RbEDeu%2BWjrL3TcYQFTFd8Q%2BvcwKkpjbmKGpmMnYuTc7HSKrRWLLGxixsLBSjKdQDK4Tu14%2F0ROJVJo4Gv%2FX4oknQ%3D%3D&upload_id=AAANsUlwcmdpeCuME5YbeSpnfw5eQw_Sb65xl7t59b6GcNkNE0PUfe44tUDXHfobXRo-EBGI6X-I5zPqXyPBm4paSyBGyzZCWw
Issues:
URL
, but the link
doesn't expire after one minute.xyz.jpg
using HTTP PUT
, it returns 200 OK
, even though it doesn't
actually upload xyz.jpg
and replace the original file.Shouldn't the upload link invalidate itself once it is used to upload something to the server? Am i missing something here? Could really use some help.
Upvotes: 0
Views: 713
Reputation: 5829
Google Cloud's Libraries for creating a signed URL are actually a lot simpler than building it "manually", as you can see on the sample code example in this official documentation. Following that, your getSignedUrlToPost
function could look like this:
private String getSignedUrlToPost(String bucketName, String objectName, String mimeType)
throws StorageException {
try {
Storage storage = StorageOptions.newBuilder().setProjectId("YOUR_PROJECT_ID")
.build().getService();
// Define Resource
BlobInfo blobInfo = BlobInfo.newBuilder(BlobId.of(bucketName, objectName))
.build();
// Generate Signed URL
Map<String, String> extensionHeaders = new HashMap<>();
extensionHeaders.put("Content-Type", mimeType);
//setting it to expire 10 minutes
return storage.signUrl(
blobInfo,
10,
TimeUnit.MINUTES,
Storage.SignUrlOption.httpMethod(HttpMethod.POST),
Storage.SignUrlOption.withExtHeaders(extensionHeaders),
Storage.SignUrlOption.withV4Signature());
} catch (StorageException e) {
throw new StorageException(500,
"Something went wrong while encoding the URL: "
e.getMessage());
}
}
I find it always best to use the official libraries as they often simplify your code and it's less error prone since you don't have to figure out what needs to be done behind the courtains.
EDIT
For the expiration of the URL after a single use, I did some research and the only thing I could find is this community question were the most upvoted answer says that this is currently not possible.
Although it is an old post (5+ years), there is no mention of that being changed in the documentation, so I assume that this is still valid. I would suggest that, in order to workaround this issue, you make the URL have a short time expiration limit and adjust it to your app's needs.
NOTE: You can find more details on the options for the signedURLs for storage in the in the javadoc for that class.
Upvotes: 1