Reputation: 1486
I'm having problems uploading a file to Amazon S3. I've developed a Grails RESTful service which uses the AWS Java SDK to generate pre signed URLs. When the client uploads a file, it first retrieves a pre-signed URL and then uses this to upload the file directly to my S3 bucket. So I have a Grails service which creates a pre signed URL like so...
def generateFileUploadUrl(AmazonS3Client client, String bucketName, String key, int expiryMins) {
GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, key);
req.setMethod(HttpMethod.POST);
req.setExpiration(getExpiration(expiryMins));
return client.generatePresignedUrl(req);
}
And then the client retrieves a URL with the following format...
https://{bucketname}.amazonaws.com/{key}?AWSAccessKeyId={accesskey}&Expires={expiry}&Signature={signature}
Then the client creates a POST request using Danial Farid's Angular File Upload module like so...
Upload.upload({
url: destUrl, // url shown above
file: file
}).progress(function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
}).success(function (data, status, headers, config) {
console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
}).error(function (data, status, headers, config) {
console.log('error status: ' + status);
});
At first I received errors about CORS settings but after editing the CORS origin configuration in my Bucket's permissions, I started getting a 403 forbidden response instead. The message in the 403 response is 'The request signature we calculated does not match the signature you provided. Check your key and signing method.'. The AWS Access Key and the Signature provided match up so I'm not sure what the exact error is.
Is my request missing some extra information? Looked at a few other posts, like this, which manually creates a policy document to send with the URL but it doesn't use the AWS Java SDK.
As it happens, my approach works fine with GET requests, and I can retrieve documents. Just can't upload.
Upvotes: 4
Views: 2270
Reputation: 25491
I know this is an old question but it still seems relevant and still seems quite tricky to get this working. The S3 signed URL upload is quite particular about the fields used in the request and the response and the error messages are not too helpful as you are seeing - I assume this is the intent for security reasons but it does make it hard to debug.
There also have been changes to the AWS signature process - the version at this time (March 2016) is AWS Signature version 4 (http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). You need to be careful when looking at examples and Stackoverflow etc that the information relates to the same signature version you are using as they don't mix well.
I started using the AWS SDK for an angular/node file upload but eventually found it easier to generate the policy on the server (node.js) side without the SDK. There is a good example (albeit node, not Grails based) here: https://github.com/danialfarid/ng-file-upload/wiki/Direct-S3-upload-and-Node-signing-example (but note the issue with the S3 bucket name here: AngularJs Image upload to S3 ).
One key thing to watch is that you correctly include the file content type in the policy generation and that this content type properly matches the content type of the file you are actually uploading.
For reference this is the code on the angular side, which works for me:
$scope.upload = function (file) {
console.log("WebUploadCtrl upload");
console.log("WebUploadCtrl sending S3sign request");
var query = {
filename: file.name,
type: file.type
};
$http.post('/api/s3sign', query).success(function(response) {
console.log("WebUploadCtrl s3sign response received");
var s3ResponseParams = response;
console.log("WebUploadCtrl upload AWSAccessKeyId: " + response.AWSAccessKeyId);
console.log("WebUploadCtrl upload signature: " + response.Signature);
$scope.upload = Upload.upload({
url: s3ResponseParams.url, //s3Url
transformRequest: function(data, headersGetter) {
var headers = headersGetter();
delete headers.Authorization;
return data;
},
fields: s3ResponseParams.fields, //credentials
method: 'POST',
file: file
}).progress(function(evt) {
$scope.progressPerCent = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + $scope.progressPerCent);
}).success(function(data, status, headers, config) {
// file is uploaded successfully
console.log('file ' + config.file.name + 'is uploaded successfully. Response: ' + data);
}).error(function() {
// Some error has occured
console.log('Error uploading to S3');
});
});
};
Upvotes: 1