使用AFNetworking预先签名的S3 URL从iOS应用程序上传

时间:2014-06-30 07:47:50

标签: amazon-web-services amazon-s3 afnetworking afnetworking-2

我正在尝试将图像从我的iPhone应用程序上传到S3,然后将S3网址存回我的rails应用程序。我不应该在iOS应用程序中嵌入凭据,所以我采取的方法是:

我尽力遵循我在网上找到的所有指示,但它不起作用,第3步的结果返回错误401禁止。由于我是新手,我甚至不知道自己做错了什么。

在第2步中,我的代码如下所示:

def getS3Url
  s3 = AWS::S3.new(
    :access_key_id => "MY S3 KEY",
    :secret_access_key => "MY SECRET ACCESS KEY"
  )
  object = s3.buckets[params["bucket"]].objects[params["path"]]
  @s3url = object.url_for(:write, { :expires => 20.minutes.from_now, :secure => true }).to_s
end

从第2步返回的网址如下所示:https://s3.amazonaws.com/myapp-bucket-name/images/avatar/user1.png?AWSAccessKeyId=[access key id]&Expires=[expiration timestamp]&Signature=[Signature]

一旦我获得该URL,我会尝试通过执行以下操作发布到该网址:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:[responseObject valueForKey:@"s3url"] parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
  [formData appendPartWithFileData:jpegData name:@"file" fileName:self.filename mimeType:@"image/png"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
  NSLog(@"Success: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
  NSLog(@"Error: %@", error);
}];

在此代码中,我使用[responseObject valueForKey:@"s3url"]从返回的对象中提取url,然后将其作为要发布的URL传递。但它不起作用。这是我在XCode中运行时的日志:

错误:错误域= AFNetworkingErrorDomain代码= -1011“请求失败:禁止(403)”UserInfo = 0x156daaf0 {NSErrorFailingURLKey = https://s3.amazonaws.com/myapp-bucket-name/images/avatar/user1.png?AWSAccessKeyId=[access密钥ID]&amp; Expires = [到期时间戳]&amp;签名= [签名],NSLocalizedDescription =请求失败:禁止(403),NSUnderlyingError = 0x156aef90“请求失败:不可接受的内容类型:application / xml”,AFNetworkingOperationFailingURLResponseErrorKey = {URL:https://s3.amazonaws.com/myapp-bucket-name/images/avatar/user1.png?AWSAccessKeyId=[access密钥ID]&amp; Expires = [到期时间戳] &amp; Signature = [Signature]} {status code:403,headers {     连接=关闭;     “Content-Type”=“application / xml”;     Date =“Mon,30 Jun 2014 07:21:33 GMT”;     Server = AmazonS3;     “转移编码”=身份;     “x-amz-id-2”=“FJwEeOjV1 / osJKgKeHO + / OjXVBEbvW09XxNX2kn1UYIuHswU + LKh0mJODRJDNLXm”;     “x-amz-request-id”= 46E84D0967B6D4CD; }}

此时我甚至都不知道自己做错了什么。也许我甚至没有发布到正确的URL。也许我需要做的不仅仅是POST。我花了整个周末试图解决这个问题而失败了。有人可以帮忙吗?感谢。

2 个答案:

答案 0 :(得分:6)

我遇到了类似的挑战&#34;。我必须使用AFNetworking 2.0将图像上传到带有来自我服务器的预签名URL的S3存储桶。在我的许多尝试和错误尝试之一,我得到了相同的403错误,发生在我身上的是我必须在请求中放置正确的标题:

  • Content-Type使用图像的mime类型
  • x-amz-acl为我的广告投放管理配置public-read

Content-Length似乎是可选的,请注意我还没有以多部分的形式上传图片。

所以这就是我最终做的事情:

+(void) uploadImage:(UIImage *)image atUrl:(NSString *)url withMimeType:(NSString *)mimeType withSuccess:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure {
    NSData *imageData = UIImageJPEGRepresentation(image, 0.1);
    NSURL *requestURL = [NSURL URLWithString:url];
    AFHTTPSessionManager *client = [[AFHTTPSessionManager alloc] initWithBaseURL:requestURL];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    [request setHTTPMethod:@"PUT"];
    [request setValue:mimeType forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:imageData];
    [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[imageData length]] forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"public-read" forHTTPHeaderField:@"x-amz-acl"];
    [request setURL:requestURL];

    NSURLSessionDataTask *task = [client dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error == nil) {
            if (success) {
                success(responseObject);
            }
        } else {
            if (failure) {
                failure(error);
            }
        }
    }];
    [task resume];
}

其中url是我从服务器获得的预签名网址。检查我在0.1的JPEG压缩,因为您可能需要不同的压缩。在我看来,图像质量并不重要。

答案 1 :(得分:1)

添加到josebama的上述答案,

我不需要添加&#34; x-amz-acl&#34;标题字段,但我添加了&#34; x-amz-date&#34;和#34;授权&#34;头。这两个标头都是从处理与Amazon服务通信的API以及签名URL返回的。仅当我添加上述两个标头值时,才能成功上传到URL。

简单地包括&#34; x-amz-acl&#34;在我的情况下,标题会导致上传失败。

也许某些服务器端参数不同或者亚马逊的某些设置参数可能会有所不同,不用说,对我有用的解决方案可能对其他人不起作用,所以看一下你的后端设置可能不错。